{  File:  [Idun]<WDLion>CPSubs.asm

Modification History:
Last Change by Jim Frandeen April 29, 1982  1:10 PM
Rewritten by Jim Frandeen to move page mapping and page crossing into the CP microcode (March 10, 1982  7:31 AM)
Created by Roy Ogus (May 13, 1980  3:37 PM)}

;  DEFINITION FILES:

	get "SysDefs"
	get "CommonDefs"

	IMP	ErrorReport		;From Common
	IMP	PortBusyFlag		;From BookKeepingTask
	IMP	ReadDmaCompletion	;From DmaSubs
	IMP	StartCPDmaChannel	;From DmaSubs

	EXP	ByteToWord
	EXP	CheckCPDMAComplete
	EXP	Copy
	EXP	DoNakedNotify
	EXP	LastPCB			;***DEBUGGING
	EXP	ReadCPbuffer
	EXP	StartCPReadDMA
	EXP	StartCPWriteDMA
	EXP	WordToByte		
	EXP	WriteCPbuffer



{NOTES:

These are the subroutines which handle transfers through the CP-IOP port. Clients using the port should use them to carry out any transfers. The client should first check PortBusyFlag to be sure the CP port is not busy. The flag is set whenever the CP DMA channel is started, and it is reset whenever the CP DMA channel completes.  

For most operations, a pointer to a CP port control block (PCB) is passed as a parameter.  The PCB contains the information needed to carry out the transfer, viz.  The CP Address, the number of bytes to be transferred, and the IOP address. The CP address is always virtual.  

CP Port Control Block Format:
	byte 0,1, 2: CP buffer pointer (low bytes first),
	byte 3: Not used
	byte 4,5: CP Buffer Size (in bytes),
	byte 6,7: IOP buffer pointer.

NOTE: the last two words of the PortControlBlock are the same as the DmaControlBlock.

The following are the subroutines available to clients:
  Transfer subroutines:
  -  ReadCPbuffer [HL: POINTER to CPPortControlBlock]
  -  WriteCPbuffer [HL: POINTER to CPPortControlBlock]
  -  StartCPReadDMA [HL: POINTER to CPPortControlBlock]
  -  StartCPWriteDMA [HL: POINTER to CPPortControlBlock]
  -  CheckCPDMAComplete [] RETURNS [ConditionCode: DMAComplete]
  Naked notify:
  -  DoNakedNotify [HL: NakedNotifyMask]
  Utility subroutines:
  -  WordToByte [HL: word] RETURNS [HL: word]
  -  ByteToWord [HL: word] RETURNS [HL: word]
  -  Copy [HL: POINTER to Source, D,E: POINTER to Destination, C: count]
}

{The following PCB is used to send a NakedNotify.  We send the CPAddress just as if it were a normal transfer command.}
NotifyPCB:
	DW	0
	DW	0
NotifyMask:
	DS	2
LastPCB:
	DW	0		;***DEBUGGING
;
ReadCPbuffer:
{Read from CP main memory.
On entry:  HL = Pointer to CPport Control Block}

	MVI	A,CPReadCmd	
	CALL	InitCPCmd	;Initialize the CP port for reads (send address, count)
{On return, count is in D,E and HL points to count high of the PCB}
	INX	H		;HL points to low IOP address
	MOV	A,M		;Get low IOP address
	INX	H		;HL points to high IOP address
	MOV	H,M
	MOV	L,A		;HL contains IOP address
{Now do the actual data transfer. The bytes coming from the port are stored directly in memory. The bytes come from the port high byte, low byte.}

ReadCPLoop:
	IN	CPStatus	;Read the port interrupt bits
	ANI	CPInIntMask	;CPIn requesting an interrupt? 
	CZ	ReadCPByteNotThere		;z => no interrupt
	IN	CPIn		;get data
	STA	HighByte
	IN	CPStatus	;Read the port interrupt bits
	ANI	CPInIntMask	;CPIn requesting an interrupt? 
	CZ	ReadCPByteNotThere		;z => no interrupt
	IN	CPIn		;get data
	MOV	M,A		;Store low byte
	INX	H		;Point to high byte
	DB	opMVIM		;Store HighByte
HighByte:
	DB	0
	INX	H		;Point to next byte
	DCX	D		;Check for end of buffer
	DCX	D		;decrement 2 for 2 bytes
	MOV	A,D
	ORA	E		;Test high and low for zero
	JNZ	ReadCPLoop
	RET
WriteCPbuffer:
{Write into CP main memory.
On entry:  HL = Pointer to CPport Control Block}
	MVI	A,CPWriteCmd	
	CALL	InitCPCmd	;Initialize the CP port for writes (send address, count)
;  On return, count is in D,E and HL points to count high of the PCB

	INX	H		;HL points to low IOP address
	MOV	A,M		;Get low IOP address
	INX	H		;HL points to high IOP address
	MOV	H,M
	MOV	L,A		;HL contains the IOP address

{The bytes coming from the memory are stored directly into the port. The bytes must be sent high byte, low byte.}

WriteCPLoop:
	MOV	A,M		;Get low byte
	PUSH	PSW		;Save low byte in Stack
	INX	H		;Point to high byte
	MOV	A,M		;Get high byte
	OUT	CPOut		;Output data
	IN	CPStatus	;Read the port interrupt bits
	ANI	CPOutIntMask	;data read? 
	CZ	WriteCPByteNotThere	;Zero means no interrupt
	POP	PSW		;Get low byte in A
	OUT	CPOut		;Output data
	IN	CPStatus	;Read the port interrupt bits
	ANI	CPOutIntMask	;data read? 
	CZ	WriteCPByteNotThere	;Zero means no interrupt
	INX	H		;Point to next byte
	DCX	D		;Check for end of buffer
	DCX	D		;decrement 2 for 2 bytes
	MOV	A,D		;Check high and low for zero
	ORA	E
	JNZ	WriteCPLoop
	RET

StartCPReadDMA:
{Start the DMA controller reading a buffer from CP main memory to IOP memory. The address in CP main memory is a virtual address.  The Client will wait for the CP Dma completion.
On entry:  HL = Pointer to CPport Control Block}

	MVI	A,CPReadCmd	;Write command
	CALL	InitCPCmd	;Initialize the CP port for reads 

{On return, count is in D,E and HL points to count high of PCB}
	DCX	H		;Point to Dma Control Block
	MVI	A,DmaFuncWrite	;Dma write
	CALL	StartCPDmaChannel	

{Dma is now programmed and enabled. Start Dma going.  At this point IOPWait, and SwTAddr are cleared.}
	MVI	a,CPEnable+CPDmaIn
	OUT	CPControl	;Set DmaIn (Clear IOPWait, clear SwTAddr, DmaMode)
	MVI	a,CPEnable+CPDmaIn+CPDmaMode
	OUT	CPControl	;Set DmaMode (set DmaIn,  clear IOPWait, SwTAddr)
	STA	PortBusyFlag
	RET

StartCPWriteDMA:
{Start the DMA controller writing a buffer to CP main memory from IOP memory. The address in CP main memory is a virtual address. The Client will wait for the CP Dma completion.
On entry:  HL = Pointer to CPport Control Block}

	MVI	A,CPWriteCmd	;Write command
	CALL	InitCPCmd	;Initialize the CP port for writes 

{On return, count is in D,E and HL points to count high of PCB}
	DCX	H		;Point to Dma Control Block
	MVI	A,DmaFuncRead	;Dma read
	CALL	StartCPDmaChannel

{Dma is now programmed and enabled. Start Dma going.  At this point IOPWait, and SwTAddr are cleared.}
	MVI	a,CPEnable+CPDmaMode
	OUT	CPControl	;Set DmaMode (clear IOPWait, SwTAddr, DmaIn)
	STA	PortBusyFlag
	RET

CheckCPDmaComplete:
{Check for the CP DMA channel completion. If the channel has completed, clear the DMA channel, and CP Dma hardware.
On exit, condition code is set:
  zero => channel is not complete
  # Zero => channel has completed, DMA channel has been cleared.}

	IN	CPStatus	;Check for completion in CPStatus register
	ANI	CPDmaCompMask
	RZ			;z => channel has not completed

	MVI	E,CPChannelMask	;Check internal completion.
	CALL	ReadDmaCompletion	; Get completion
	JZ	NoCPDmaComplete	;nz => Completed

CPDmaCompleted:
{The CP channel completed correctly. The Dma controller hardware is disabled, and ReadDmaCompletion will clear the channel in DmaActive.  Thus, don't CALL ClearDmaChannel. Clear the external Dma completion bit, and clear DmaMode. We also clear PortBusyFlag here.}
	XRA	A
	STA	PortBusyFlag
	OUT	CPDmaClr	;Clear CPDmaComplete bit
	MVI	A,CPEnable
	OUT	CPControl	;Clear DmaMode (clear Wait, SwTAddr, DmaIn)
	ORA	A		;Set condition code for completed
	RET


NoCPDmaComplete:
{The Dma channel did not internally complete.  This indicates a fatal error.}
	LXI	h,ErrorNoCPDmaComplete	;ERROR:  Internal CP Dma channel did not complete
	JMP	ErrorReport

DoNakedNotify:
{On entry:  HL = Notify mask.}

	SHLD	NotifyMask	;Save mask in NotifyPCB
	LXI	H,NotifyPCB	;Point to NotifyPCB
	MVI	A,CPNotifyCmd
{Fall through to InitCPCmd}
;
InitCPCmd:
{Initialize CP port for reads or writes.
On entry:
    A = command to microcode
    HL = Pointer to CPport Control Block
  Format of initialize:
    low address, middle address, high address, low count, high count, Command.
  On Exit:
    HL = pointer to count high of CP port Control Block
    DE contains count in bytes or NakedNotify mask} 

	PUSH	PSW		;Save command in A
	SHLD	LastPCB		;***DEBUGGING
	MOV	A,M		;Address byte 3
	OUT	CPOut		;Output data
	IN	CPStatus	;Read the port interrupt bits
	ANI	CPOutIntMask	;data read? 
	CZ	WriteCPByteNotThere	;Zero means no interrupt
	INX	H
	MOV	A,M		;Address byte 2
	OUT	CPOut		;Output data
	IN	CPStatus	;Read the port interrupt bits
	ANI	CPOutIntMask	;data read? 
	CZ	WriteCPByteNotThere	;Zero means no interrupt
	INX	H
	MOV	a,M		;High part address (address byte 1)
	OUT	CPOut		;Output data
	IN	CPStatus	;Read the port interrupt bits
	ANI	CPOutIntMask	;CPOut requesting an interrupt, i.e data read? 
	CZ	WriteCPByteNotThere		;Zero means no interrupt
	INX	H		;Point to byte not used
	INX	H		;Point to count low
	MOV	A,M		;Low part count
	MOV	E,A		;DE will contain count on return
	OUT	CPOut		;Output data
	IN	CPStatus	;Read the port interrupt bits
	ANI	CPOutIntMask	;CPOut requesting an interrupt, i.e data read? 
	CZ	WriteCPByteNotThere		;Zero means no interrupt
	INX	H		;Point to count high
	MOV	A,M		;D,E contains count
	MOV	D,A
	OUT	CPOut		;Output data
	IN	CPStatus	;Read the port interrupt bits
	ANI	CPOutIntMask	;CPOut requesting an interrupt, i.e data read? 
	CZ	WriteCPByteNotThere		;Zero means no interrupt
	POP	PSW		;A ← command
	OUT	CPOut		;Output data
	IN	CPStatus	;Read the port interrupt bits
	ANI	CPOutIntMask	;CPOut requesting an interrupt, i.e data read? 
	CZ	WriteCPByteNotThere		;Zero means no interrupt
	RET

WriteCPByteNotThere:
{WriteCP byte is not there.  Check for timeout.}
	CMA			;A ← FF
UpdateWriteTimeout:
	STA	WriteTimeout
	IN	CPStatus	;Read the port interrupt bits
	ANI	CPOutIntMask	;CPOut requesting an interrupt, i.e data read? 
	RNZ

	DB	opMVIA		;A ← WriteTimeout
WriteTimeout:
	DB	0FFH
	DCR	A
	JNZ	UpdateWriteTimeout

WritePortTimeOut:
	LXI	h,ErrorWriteCPPortDead	;Set up error code for read timeout
	JMP	PortTimeOut


ReadCPByteNotThere:
{ReadCP byte is not there.  Check for timeout.}
	CMA			;A ← FF
UpdateReadTimeout:
	STA	ReadTimeout
	IN	CPStatus	;Read the port interrupt bits
	ANI	CPInIntMask	;CPIn requesting an interrupt? 
	RNZ

	DB	opMVIA		;A ← ReadTimeout
ReadTimeout:
	DB	0
	DCR	A
	JNZ	UpdateReadTimeout

ReadPortTimeOut:
	LXI	H,ErrorReadCPPortDead	;Set up error code for read timeout

PortTimeOut:
{We have a timeout.  Check for CP parity error first.}
	IN	MiscInput1
	ANI	CSParityMask
	JNZ	ErrorReport		;z => (active low) it was
	LXI	h,ErrorCSParity		;ERROR:  CS parity error
	JMP	ErrorReport
;
ByteToWord:
{Convert value in HL (byte count) to word count. i.e. long shift right by 1.}
	XRA	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

WordToByte:
{Convert value in HL (word count) to byte count. i.e. long shift left by 1.}
	XRA	A		;Clear carry
	MOV	A,L		;Low part
	RAL			;shift low part, high bit into carry
	MOV	L,A
	MOV	A,H		;High part
	RAL			;shift high part, shift in low bit
	MOV	H,A
	RET

Copy:
{On entry:
	HL = Source address
	DE = Destination address
	A = Count (in bytes).  Count=0 => 256.
  On exit:
	HL = Next Source address after copy
	DE = Next Destination address after copy}

	STA	CopyCount
	MOV	A,M		;Get source byte
	STAX	D		;Store away in destination
	INX	H		;Increment pointers
	INX	D
	DB	opMVIA		;A ← CopyCount
CopyCount:
	DB	0
	DCR	A		;Check count
	JNZ	Copy
	RET

	END	CPSubs