{	File: [Iris]<WMicro>DLion>RS232CGet.asm

Modification History:
  
  Dennis Grundler:   6-Nov-84 17:14:25: Pick up Michael Thatcher's fix for check of LastBufferFlag of 21-Aug-84 17:12:40
  Dennis Grundler:   1-Sep-84 17:09:06  Added copyright notice
  Michael Thatcher: 21-Dec-83 14:39:46: Add Async Flow Control Handling
  Dennis Grundler:  9-Oct-83 16:32:55: Cleanup code.
  Amy Fasnacht : 13-Jun-83 16:05:12: Add CTS test at start of task
  Chuck Fay : 10-Nov-82 17:58:24: Fixed FifoOverflow handling.
  Jim Frandeen :  September 17, 1982  9:01 AM: allow for async blocks to be bigger
    than the client's buffer.
  Jim Frandeen :  September 9, 1982  8:39 AM: turn off Success bit when error code
    is returned. Change handling of Abort.
  Jim Frandeen :  July 30, 1982  1:30 PM: new IO Page format. Add CRC error as 20H
    and FramingError as 8.
  Jim Frandeen :  March 14, 1982  2:23 PM: created file}
  
{ 	Copyright (C) 1982, 1983 by Xerox Corporation.  All rights reserved.}


	Get "SysDefs"	
	Get "CommonDefs"
	Get "RS232CDefs"

	IMP	ByteToWord		;From CPSubs
	IMP	CharLengthMask		;From RS232CInterrupts
	IMP	CharLengthMask1		;From RS232CInterrupts
	IMP	CharactersLeftInClientBuffer	;From RS232CInterrupts
	IMP	CharactersUntilBoundary	;From RS232CInterrupts
	IMP	CheckCPDmaComplete	;From CPSubs
	IMP	CurrentCharLengthMask	;From RS232CMisc
	IMP	CurrentFrameToFillPtr	;From RS232CInterrupts
	IMP	CurrentFrameToSendPtr	;From RS232CInterrupts
	IMP	DmaActive		;From CPSubs
	IMP	DoNakedNotify		;From CPSubs
	IMP	FCState			;From RS232CInterrupts
	IMP	FifoStateSwitch		;From RS232CInterrupts
	IMP	FirstFrameToFillPtr	;From RS232CInterrupts
	IMP	FirstMaxFrameBoundary	;From RS232CInterrupts
	IMP	FirstRxCharPtr		;From RS232CInterrupts
	IMP	FlowControlFlag		;From RS232CInterrupts
	IMP	LastBufferFlag		;From RS232CInterrupts
	IMP	LastNewFrameAddressHigh	;From RS232CInterrupts
	IMP	LastNewFrameAddressLow	;From RS232CInterrupts
	IMP	MaxFrameSize		;From RS232CInterrupts
	IMP	MainLoop		;From BookKeepingTask

	IMP	OutputXon
	IMP	PortBusyFlag		;From CPSubs
	IMP	ReadCPBuffer		;From CPSubs
	IMP	RS232CGetFlag		;From BookKeepingTask
	IMP	RS232CMode		;From RS232CMisc
	IMP	RS232CTaskWakeMask	;From RS232CMisc
	IMP	RxBisyncStateSwitch	;From Common
	IMP	RxBisyncInitial		;From BisyncInput
	IMP	RxFIFO			;From Buffer
	IMP	RxFifoEnd		;From RS232CInterrupts
	IMP	RxFifoState		;From RS232CInterrupts
	IMP	RxMaxFrameSize		;From BisyncInterrupts
	IMP	RxWR3			;From RS232CMisc
	IMP	SetLastBufferFlag	;From RS232CInterrupts
	IMP	SdlcFrameFlag		;From RS232CInterrupts
	IMP	TerminateFrame		;From RS232CInterrupts
	IMP	TerminateBisyncFrame	;From BisyncInput
	IMP	TestAsyncTimer		;From RS232CSubs
	IMP	TxWR1			;From RS232CMisc
	IMP	WriteCPBuffer		;From CPSubs
	IMP	ZeroCommand		;From Common

	EXP	AbortGetFlag
	EXP	AsyncGetLoop
	EXP	BisyncGetLoop
	EXP	FloppyActiveSwitch4
	EXP	GetByteCount
	EXP	GetFifoOverflow
	EXP	GetModeSwitch
	EXP	GetTaskResumeAddress
	EXP	GetTaskStartAddress
	EXP	GetTaskQuiet
	EXP	PrevFrameToSendPtr	;***DEBUGGING
	EXP	SdlcGetLoop
	EXP	StartGetTask
	EXP	StatusChangeFlag
	EXP	TestStatusChange

;
RS232CGetTask:
	DB	opJMP	
GetTaskStartAddress:
	DW	GetTaskQuiet
;	JMP	GetTaskQuiet		; Set by Misc Off command
;	JMP	NotifyStatusChange	; Set in code below
;	JMP	TestStatusChange	; Set by Misc SetParameters

{Check StatusChangeFlag. This gets set by the External Interrupt routine if we detect CTS (Clear to Send) or DCD (Data Carrier Detect).  If this is set, we do a Naked Notify.}
TestStatusChange:
	LDA	StatusChangeFlag
	ORA	A
	JZ	RS232CTaskStart	

NotifyStatusChange:
	LDA	PortBusyFlag
	ORA	A
	JZ	SendStatusNotify
	LXI	H, NotifyStatusChange
	SHLD	GetTaskStartAddress
	JMP	RS232CTaskStart

SendStatusNotify:
	XRA	A
	STA	StatusChangeFlag
	LHLD	RS232CTaskWakeMask
	CALL	DoNakedNotify
	LXI	H, TestStatusChange
	SHLD	GetTaskStartAddress

RS232CTaskStart:
	DB	opJMP	
GetTaskResumeAddress:
	DW	GetTaskQuiet	
;	JMP	WaitForIocbTransfer
;	JMP	StartGetTask	;Set by Misc SetParameters Command
;	JMP	WaitForIocb
;	JMP	GetLoop
;	JMP	SendFrame
;	JMP	WaitForDataTransfer
;	JMP	ReturnGetIocb
;

StatusChangeFlag:
	DB	0

RS232CGetCSB:	
GetIOCBAddressPointerLo:
	DS	2		;14033
GetIOCBAddressPointerHi:
	DS	2		;14034

{The Iocb is arranged so that we can write the Iocb, the CSB, and the NakedNotify in one transaction. Note that the IOCB is in reverse byte form because it is read and written with the DMA.}

RS232CGetIocbSize	EQU	10

{This is the Get IOCB that is transferred to and from the CP.}
GetIocbBuffer:
GetBufferAddressLo:
	DB	0
GetBufferAddressMid:
	DB	0		; blockPointer: LONG POINTER
GetBufferAddressHi:
	DB	0
	DB	0		;Not used
GetByteCount:
	DB	0		; byteCount: CARDINAL 
GetByteCountHi:
	DB	0
GetDataByteCount:
	DB	0		; returnedByteCount: CARDINAL
GetDataByteCountHi:
	DB	0		
GetTransferStatus:	
	DB	0		; transferStatus: iopTransferStatus
GetTransferStatusHi:	
	DB	0		

GetIocbPcb:
{ Port Control Block to Read and Write the Get IOCB.}
GetPcbAddressLo:
	DW	0
GetPcbAddressHi:
	DW	0			; = 0 (0 MDS)
	DW	RS232CGetIocbSize	; Size in bytes
	DW	GetIocbBuffer

RS232CGetCPPort:
{ Port Control Block to read the RS232C CSB from the IOPage }
	DW	RS232CGetCSBLoc	; CP Buffer Pointer Low
	DW	CPIOPageHi	; CP Buffer Pointer Hi
	DW	RS232CGetCSBSize	; Size in bytes
	DW	RS232CGetCSB	; Pointer to Buffer in Common

ResetGetFlag:
{ Port Control Block to reset the Get flag in the IOPage }
	DW	RS232CGetFlagLoc	; CP Buffer Pointer Low
	DW	CPIOPageHi	; CP Buffer Pointer Hi
	DW	2		; Size in bytes
	DW	ZeroCommand

PrevFrameToSendPtr:
	DW	0		;***FOR DEBUGGING
;
{The interrupt routine puts characters into the Fifo, and the Get routine removes one frame at a time. Each time a frame is ready, the Fifo routine notifies the Get routine by posting a completion code in the frame header. The interrupt routine fills a frame and sets the FrameCompletionFlag # 0 when a frame is ready to send. The interrupt routine stores the address of the last character stored + 1 in FrameLastChar.

We never start a new frame unless there is room in the Fifo for the maximum frame size + the size of the frame header. RxMaxFrameSize contains the maximum frame size. We get an overflow when there is no room in the Fifo for a new frame.

The first frame begins at RxFifo. The next frame begins at the next address following the first frame. If the following calculation is negative, we need to start a new frame at the beginning of the RxFifo: LastNewFrameAddress - NextFrameToFillPtr.

This is what a frame looks like:}

FrameKey:
	EQU	0	;# FrameValid => frame has been stored over
FrameReadyFlag:
	EQU	1	;#0 => frame is ready to send
FrameLastCharLow:
	EQU	2
FrameLastCharHigh:
	EQU	3	;stored by interrupt routine
FrameCPAddrLow:
	EQU	4	;low byte of CP address
FrameCPAddrMid:
	EQU	5	;middle byte of CP address
FrameCPAddrHigh:
	EQU	6	;high byte of CP address
FrameSizeLow:
	EQU	7	;number of data bytes in frame
FrameSizeHigh:
	EQU	8
FrameCommand:
	EQU	9	;WriteBlock command for CP micrrocode
FrameData:
	EQU	10	;First character of data
FrameValid EQU 7EH
;
StartGetTask:
FetchGetIocb:
	LDA	RS232CGetFlag
	ORA	A
	JNZ	ReadGetCSB

GetTaskQuiet:
{Nothing to do yet because there is no Iocb Ready. Turn off the AbortGetFlag in case Misc is waiting for AbortGet.}
	XRA	A
	STA	AbortGetFlag
	JMP	GetTaskYield

{The FullFlag is on to indicate that an IOCB is ready. See if the CP port is free.}

ReadGetCSB:
	LDA	PortBusyFlag
	ORA	A
	JNZ	GetTaskYield

{Read the CSB to assure that we have the correct IOCB data.}
	LXI	H,RS232CGetCPPort
	CALL	ReadCPBuffer

{Copy IOCB address from the CSB into the Port Control Block. We know that the IOCB is in MDS zero, so don't bother to copy the high address. Then copy the IOCB from the CP into IOP memory.} 
	LHLD	GetIocbAddressPointerLo
	SHLD	GetPcbAddressLo	
	LXI	H,GetIocbPcb
	CALL	ReadCPBuffer	; Copy IOCB from Main Memory

{Initialize the address of the data buffer in CP memory. This will be incremented if the data is transferred back to the CP in blocks.}
	LHLD	GetBufferAddressLo
	MOV	A,L
	STA	BufferAddressLo
	MOV	A,H
	STA	BufferAddressMid
	LDA	GetBufferAddressHi
	STA	BufferAddressHi

	LXI	H,0
	SHLD	GetDataByteCount	;Zero bytes returned field

	LXI	H,XferSuccess	;Assume Get will be successful
	SHLD	GetTransferStatus

{Initialize the size of the CPBuffer so that the interupt routine will know when to end a frame. For Async Bisync mode, the IOCB size can be greater than 4K bytes, so we must choose a smaller number and send the data back to the CP in smaller blocks.}
	LHLD	GetByteCount
	MOV	A,L
	STA	RxClientBufferSizeLow
	MOV	A,H
	STA	RxClientBufferSizeHigh
	LDA	RS232CMode
	CPI	CPSDLCMode
	JZ	SetMaxFrameSize	;if SDLC

	LXI	H,600	;Set max block size if Async or Bisync

SetMaxFrameSize:
	SHLD	RxMaxFrameSize

{Enable Rx if not yet enabled. We must not enable until we have received the first IOCB so we will know how big the CP buffer is. The interrupt routine needs to know when it fills the IOCB.}
	LDA	RxWR3
	ANI	RxEnable
	JNZ	GetLoop
;
{Start here when the first IOCB is ready and we have not yet done our first Get. We also come through here if we get an invalid frame that wipes out a frame, and we must re-initialize the Fifo. This can happen if we get a block bigger than the maximum size.}
	LDA	CurrentCharLengthMask
	STA	CharLengthMask
	STA	CharLengthMask1

{Disable interrupts here. If we are re-initializing because of a Fifo overflow, we don't want the interrupt routine to change anything until we have re-initialized.}
	DI

{HL = max frame size. Initialize CharactersUntilBoundary to the size of the frame. We assume that our first boundary will be when we fill the first frameor client buffer. The FifoState starts in the Open sate.}
	SHLD	CharactersUntilBoundary
	LHLD	GetByteCount
	SHLD	CharactersLeftInClientBuffer
	MVI	A,opJNZ		;Set FifoState to Open
	STA	FifoStateSwitch
	STA	RxFifoState

{Initialize the Fifo address. Note that CurrentFrameToFillPtr points to FrameCompletionCode, and other pointers point to FrameValid.}
	LXI	H,RxFIFO	;HL points to RxFifo
	SHLD	FirstFrameToFillPtr
	SHLD	CurrentFrameToSendPtr
	SHLD	CurrentFrameToSendPtr1
	SHLD	CurrentFrameToSendPtr2

	XRA	A
	STA	StatusChangeFlag
	STA	SdlcFrameFlag
	MVI	M,FrameValid
	INX	H		;HL points to FrameCompletionFlag
	SHLD	CurrentFrameToFillPtr
	MOV	M,A		;Turn off FrameCompletionFlag
	LXI	D,FrameData-1
	DAD	D		;HL points to first data char of first frame
	SHLD	FirstRxCharPtr

{Set NextRxCharPtr. Note that BC are reserved for this. BC are not used anywhere else in Domino.}
	MOV	B,H
	MOV	C,L		;BC ← NextRxCharPtr

	LXI	H,RxBisyncInitial
	SHLD	RxBisyncStateSwitch
	EI

	LXI	H,ReturnGetIocb
	SHLD	FrameSentSwitch
	LXI	H,YieldForTransfer
	SHLD	WaitForTransferSwitch

{Calculate the minimum frame size: RxMaxFrameSize + FrameData. The interrupt routine needs to know if there is room for another frame in the Fifo.}
	LHLD	RxMaxFrameSize
	LXI	D,FrameData
	DAD	D		;HL ← MaxFrameSize
	SHLD	MaxFrameSize
	XCHG			;DE ← MaxFrameSize
	LHLD	FirstFrameToFillPtr
	DAD	D
	SHLD	FirstMaxFrameBoundary

{Calculate the last new frame address in the RxFifo: FifoBoundary - MaxFrameSize. When a frame ends, the interrupt routine needs to know if we can start another frame following the previous frame, or if we should start the next frame at the beginning of the RxFifo. If the following calculation is negative, we need to start at the beginning of the RxFifo: LastNewFrameAddress - NextNewFrameAddress.}
	LHLD	RxFifoEnd
	MOV	A,L
	SUB	E		;Subtract MaxFrameSize
	STA	LastNewFrameAddressLow
	STA	LastNewFrameAddressLow1
	MOV	A,H
	SBB	D
	STA	LastNewFrameAddressHigh
	STA	LastNewFrameAddressHigh1

{Turn on the receiver.}
	LXI	H,8000H+RxCont
	LDA	RxWR3
	ORI	RxEnable
	STA	RxWR3
	DI
	MVI	M,PointToWR3+ResetRxCRCChecker
	MOV	M,A
	EI

{Now that WR3 is set up to enable Rx, we will start getting interrupts from the SIO chip, and we can start receiving data.}

GetLoop:
	DB	opJMP
GetModeSwitch:
	DW	AsyncGetLoop
;	JMP	AsyncGetLoop
;	JMP	SdlcGetLoop
;	JMP	BisyncGetLoop
;
BisyncGetLoop:
{Test to see if the interrupt routine has filled a frame.}
	DB	opLHLD	;H ← FrameCompletionFlag, L ← FrameValid
CurrentFrameToSendPtr2:
	DW	0
	MOV	A,H		;A ← FrameCompletionFlag
	ORA	A
	JZ	TestBisyncAbort

BisyncFrameCompletion:
{Come here when the interrupt routine has terminated a frame.
H = FrameCompletionFlag, L = FrameValid
A indicates the reason for the termination:
	bit 0 (mask 80)	End of Frame
	bit 1 (mask 40)	CRC Error or Framing Error
	bit 2 (mask 20)	Overrun
	bit 3 (mask 10)	Parity Error
	bit 4 (mask 8)	Invalid character sequence
	bit 5 (mask 4)	End Of Block
	bit 6 (mask 2)	Fifo Overflow
	bit 7 (mask 1)	Abort
}

	MOV	A,L
	CPI	FrameValid
	JNZ	InvalidFrame
	MOV	A,H

	CPI	8
	JNZ	FrameCompletion

	MVI	L,InvalidCharacter
	JMP	StoreErrorStatus

TestBisyncAbort:
	LDA	AbortGetFlag
	ORA	A
	JNZ	AbortFlagSet

	LXI	H,GetLoop
	JMP	SaveGetResumeAddressAndYield

AbortFlagSet:
	MVI	E,1		;Set CompletionCode for Aborted
{Set up the stack so we can call the interrupt routine. We need 3 words plus a return address on the stack. CompletionCode is in E.}
	LXI	H,BisyncGetLoop
	PUSH	H		;push return address
	PUSH	PSW
	PUSH	D
	DI
	CALL	TerminateBisyncFrame	;push one more word and JMP

;
SdlcGetLoop:
{Test to see if the interrupt routine has filled a frame.}
	DB	opLHLD	;H ← FrameCompletionFlag, L ← FrameValid
CurrentFrameToSendPtr1:
	DW	0
	MOV	A,H		;A ← FrameCompletionFlag
	ORA	A
	JZ	TestAbort

SdlcFrameCompletion:
{Come here when the interrupt routine has terminated a frame.
H = FrameCompletionFlag, L = FrameValid
A indicates the reason for the termination:
	bit 0 (mask 80)	End of Frame
	bit 1 (mask 40)	CRC Error or Framing Error
	bit 2 (mask 20)	Overrun
	bit 3 (mask 10)	Parity Error
	bit 4 (mask 8)	Not Used
	bit 5 (mask 4)	Not Used
	bit 6 (mask 2)	Fifo Overflow
	bit 7 (mask 1)	Abort
}

	MOV	A,L
	CPI	FrameValid
	MOV	A,H
	JZ	FrameCompletion

InvalidFrame:
{Come here if the frame has been stored over. In SDLC mode, there is no time to check to see if we are storing over a frame to send. We must trust that we will not get a frame that is larger than the client's IOCB. If a frame gets stored, over, return an error code and reinitialize the Fifo.}

{Turn off the receiver.}
	LXI	H,8000H+RxCont
	LDA	RxWR3
	ANI	0FFH-RxEnable
	DI
	MVI	M,PointToWR3
	MOV	M,A
	EI
	STA	RxWR3
	MVI	A,DataLost
	STA	GetTransferStatusHi
	JMP	ReturnGetIocb
;
AsyncGetLoop:
{When running in Async mode, we must test for timeout. If we get a timeout, we end the frame and ship this one back to the CP.}
	LDA	LastBufferFlag
	ORA	A
	JNZ	TestCFTS
	CALL	TestAsyncTimer
	MVI	E,80H		;Set CompletionCode for Timeout
	JZ	AsyncTimeout
	
TestCFTS:
{Test to see if the interrupt routine has filled a frame.}
	LHLD	CurrentFrameToSendPtr
	INX	H		;Point to FrameCompletionCode
	MOV	A,M
	ORA	A
	JNZ	FrameCompletion

TestAbort:
	DB	opORI		;A ← AbortGetFlag
AbortGetFlag:
	DB	0		;Check for AbortInput Command
	JNZ	AbortGet	;Jump if abort

	LXI	H,GetLoop
	JMP	SaveGetResumeAddressAndYield
{We will return to GetLoop when it is our turn to run again.}

FrameCompletion:
{Come here when the interrupt routine has terminated a frame.
A = FrameCompletionFlag
A indicates the reason for the termination:
	bit 0 (mask 80)	End of Frame
	bit 1 (mask 40)	CRC Error or Framing Error
	bit 2 (mask 20)	Overrun
	bit 3 (mask 10)	Parity Error
	bit 4 (mask 8)	Not Used
	bit 5 (mask 4)	End Of Block
	bit 6 (mask 2)	Fifo Overflow
	bit 7 (mask 1)	Abort
}

	CPI	80H		;Test for End of Frame
	JZ	SendFrame
	CPI	4
	JZ	EndOfBlock

	CPI	1
	JZ	AbortDetected

	MOV	H,A
	ANI	FifoOverflowed
	JNZ	GetFifoOverflow

	MOV	A,H
	ANI	40H
	JNZ	CRCErrorDetected

	MOV	A,H
	ANI	10H
	JNZ	ParityErrorDetected

OverrunErrorDetected:
	MVI	L,DeviceError
	JMP	StoreErrorStatus

AbortDetected:
{Store Aborted in TransferStatus, 0 in TransferStatusHi.}
	MVI	L,Aborted
	JMP	StoreErrorStatus

CRCErrorDetected:
	MVI	L,CRCError
	JMP	StoreErrorStatus

FramingErrorDetected:
	MVI	L,FramingError
	JMP	StoreErrorStatus

ParityErrorDetected:
	MVI	L,ParityError

StoreErrorStatus:
	MVI	H,0		;Zero success bit
	SHLD	GetTransferStatus
	JMP	SendFrame

AbortGet:
{Come here if we get an Abort command from Misc. Abort this block. Store Aborted in TransferStatus, 0 in TransferStatusHi.}
	MVI	E,1		;Set CompletionCode for Aborted
	LXI	H,GetLoop	;Set return address
	JMP	PushReturnFromTerminateFrame

AsyncTimeout:
{When we get a timeout in Async mode, we end the current frame. Set up the stack so we can call the interrupt routine. We need 3 words plus a return address on the stack. CompletionCode is in E.}
	LXI	H,SendFrame
PushReturnFromTerminateFrame:
	PUSH	H		;push return address
	PUSH	PSW
	PUSH	D
	DI
	CALL	TerminateFrame	;push one more word and JMP

EndOfBlock:
{Come here if this is the end of a block in Async or Bisync mode. Send this frame back to the CP. Return to BlockSent when the transfer is complete.}
	LXI	H,BlockSent
	SHLD	FrameSentSwitch
	LXI	H,WaitForTransfer
	SHLD	WaitForTransferSwitch
	JMP	SendFrame

BlockSent:
	LXI	H,YieldForTransfer
	SHLD	WaitForTransferSwitch
	LXI	H,ReturnGetIocb
	SHLD	FrameSentSwitch

{Increment the CP buffer address.}
	LDA	BufferAddressLo
	MOV	E,A
	LDA	BufferAddressMid
	MOV	D,A
	LHLD	CurrentFrameDataBytes
	CALL	ByteToWord
	DAD	D
	MOV	A,L
	STA	BufferAddressLo
	MOV	A,H
	STA	BufferAddressMid
	LDA	BufferAddressHi
	ACI	0
	STA	BufferAddressHi

{Update the number of characters left in the IOCB so that the SendFrame routine can test to be sure we don't send more characters than will fit in the IOCB.}
	LHLD	CurrentFrameDataBytes
	LDA	RxClientBufferSizeLow
	SUB	L
	STA	RxClientBufferSizeLow
	LDA	RxClientBufferSizeHigh
	SBB	H
	STA	RxClientBufferSizeHigh
	JMP	GetLoop

GetFifoOverflow:
{Come here if the RxFifo has overflowed. Either we could not keep up with the input, or the Fifo is not big enough. The interrupt routine has stored this frame over another frame. This means it threw away a frame. Return this frame to the CP with DataLost error code
}
	MVI	A,DataLost
	STA	GetTransferStatusHi

SendFrame:
{Come here when we have a frame ready to send to the CP. The interrupt routine has stored the pointer to the last character stored into the FrameLastChar field of the frame. Prepare to send this frame to the CP. We will send the WriteBlock Command and the data in one transaction.}
	LDA	PortBusyFlag
	ORA	A
	JZ	StartFrameTransfer

	LXI	H,SendFrame
	JMP	SaveGetResumeAddressAndYield

StartFrameTransfer:
	LHLD	CurrentFrameToSendPtr
	INX	H
	INX	H		;HL points to FrameLastCharLow
	MOV	A,M		;A ← LastCharLow
	INX	H		;HL points to FrameLastCharHigh
	SUB	L		;Addr(LastChar) - Addr(FrameLastCharHigh)
	MOV	E,A
	MOV	A,M		;A ← LastCharHigh
	SBB	H
	MOV	D,A

{DE contains Addr(last char stored+1) - Addr(FrameLastCharHigh) = number of data bytes + 7}
	MOV	A,E
	SUI	7
	MOV	E,A
	MOV	A,D
	SBI	0
	MOV	D,A
	INX	H		;HL points to FrameCPAddrLow

{Now DE contains the number of data characters in the frame. Be sure the block will fit in the client's buffer. Calculate RxClientBufferSize - DE. If this is negative, the block is too big, and we may have stored over another frame in the Fifo.}
	DB	opMVIA	;A ← RxClientBufferSizeLow
RxClientBufferSizeLow:
	DB	0
	SUB	E
	DB	opMVIA	;A ← RxClientBufferSizeHigh
RxClientBufferSizeHigh:
	DB	0
	SBB	D
	JM	InvalidFrame

{DE contains the number of data characters in the frame. HL points to the first character that we will send to the CP. Send the address in HL to the DMA.}
	MOV	A,L
	OUT	DmaCh1Addr	;Send low address
	MOV	A,H
	OUT	DmaCh1Addr	;Send high address

{Make the number of data bytes even. We must send an even number to the CP.}
	XCHG			;DE points to FrameCPAddrLow 
				;HL ← count
	SHLD	CurrentFrameDataBytes
	INX	H
	MOV	A,L
	ANI	0FEH		;Make it even
	MOV	L,A		;HL ← even data byte count
	ORA	H		;Test for zero count
	JZ	UpdateCurrentFrameToSend
	PUSH	H		;Stack ← CurrentFrameEvenDataBytes

{DE points to FrameCPAddrLow. HL contains the even number of data bytes. Calculate the FrameByteCount, the next field of the CPTransferControlBlock. This is the total number of bytes we send to the CP - 1: (data byte count) + (WriteBlock Command byte count) - 1 = (data byte count) + 5 = HL + 5. FrameByteCountHigh must contain the DMA transfer command in the high two bits. For Read, we want DmaFuncRead = 80H in the high byte.}
	PUSH	D
	LXI	D,8005H	;BC ← 5 + DmaFuncRead
	DAD	D		;HL ← HL + 5 = data byte count + 5
	POP	D

{Send the count and the command to the DMA.}
	MOV	A,L
	OUT	DmaCh1Count
	MOV	A,H
	OUT	DmaCh1Count

{Store the CPAddress in FrameCPAddrLow, Mid, High}
	XCHG			;HL points to FrameCPAddrLow
	DB	opMVIM	;M ← BufferAddressLo
BufferAddressLo:
	DB	0
	INX	H		;HL points to FrameCPAddrMid
	DB	opMVIM	;M ← BufferAddressMid
BufferAddressMid:
	DB	0
	INX	H		;HL points to FrameCPAddrHigh
	DB	opMVIM	;M ← BufferAddressHi
BufferAddressHi:
	DB	0
	INX	H		;HL points to FrameSizeLow

{Store the number of data bytes in FrameSizeLow, High. }
	POP	D		;DE ← CurrentFrameEvenDataBytes
	MOV	M,E		;Store FrameSizeLow
	INX	H		;HL points to FrameSizeHigh
	MOV	M,D		;Store FrameSizeHigh

	INX	H		;HL points to FrameCommand
	MVI	M,CPWriteCmd	;Store WriteBlockCommand

{One final check to be sure the interrupt routine has not stored into our frame header. This can happen if we get a block that is bigger than the client's Iocb. We don't have time to check for this in the interrupt routine.}
	LHLD	CurrentFrameToSendPtr
	MOV	A,M
	CPI	FrameValid
	JNZ	InvalidFrame

{Now we are ready to start the DMA for the CP channel. When we send the command to the DMA controller, the low 4 bits of the command specify which channels to start. We must start the CP channel as well as any other channels that are currently busy. 

The problem is to know what channels are currently busy so we can send the proper command to the controller. DmaActive is the command we last sent to the DMA to start one or more channels. The low 4 bits of this command indicate which channels were last started. Some of these channels may have completed since then. 

In order to find out what channels have completed, we must read the Status Register. The low 4 bits of this register indicate what channels have completed. However, we cannot read this register and use this information to restart channels that have not completed, because a channel could complete in this interval. To leave a channel enabled is like pointing a loaded gun at our memory. So what me must do is to stop all channels except for the floppy channel before we read the Status Register. We must not stop the floppy channel if it is busy because it cannot afford the time. 

Set up A with the ModeSetRegister contents. If the floppy was busy, we will set the floppy active and turn all other channels off. If the floppy was not busy, we will turn off all channels.}
	LXI	H,DmaActive

{If the floppy and RS232C are running at the same time, we must disable interrupts when DmaActive is being updated. The following op code will be a NOP whenever the floppy is NOT active. It will be DI whenever the floppy IS active.}
FloppyActiveSwitch4:
	NOP
	MOV	A,M		;A ← DmaActive
	ANI	DmaCh0Mask	;A ← 1 if floppy was busy
	OUT	DmaMode	;Point to DmaMode register
	IN	DmaStatus		;Disable channels
	CMA			;A ← inverted TerminalCount Flags

{Now A contains zero in bit positions of channels that have completed and ones in all other bit positions. We AND this with DmaActive. This gives us a command to restart all channels that need to be restarted. We then OR in the bit for the CP channel, send the command to the DMA, and save DmaActive.}
	ANA	M	
	ORI	DmaCh1Mask	;Start CP Channel
	OUT	DmaMode	;Send command to DMA
	MOV	M,A		;Save DmaActive
	EI			;If disabled by FloppyActiveSwitch4

{The DMA is now programmed and enabled.}
	MVI	A,CPEnable+CPDmaMode
	OUT	CPControl	;Set DmaMode (clear IOPWait, SwTAddr, DmaIn)
	STA	PortBusyFlag

{If we are sending a block of data for Bisync mode, wait for the transfer to complete. Otherwise the frames will catch up with us and we will get a Fifo overflow.}

	DB	opJMP
WaitForTransferSwitch:
	DW	YieldForTransfer
;	JMP	YieldForTransfer
;	JMP	WaitForTransfer

WaitForTransfer:
	CALL	CheckCPDmaComplete
	JNZ	UpdateCurrentFrameToSend
	JMP	WaitForTransfer

YieldForTransfer:
	LXI	H,WaitForDataTransfer
	JMP	SaveGetResumeAddressAndYield

WaitForDataTransfer:
	CALL	CheckCPDmaComplete
	JZ	GetTaskYield
;
UpdateCurrentFrameToSend:
{Now that the transfer has completed, it is safe to update the CurrentFrameToSendPtr to point to the next frame in the Fifo.. If we update before the completion, it is possible that a frame being filled will run into the frame being sent. The interrupt routine is already filling the next frame. Set HL to point to the next character after the current frame: CurrentFrameToSendPtr + CurrentFrameDataBytes + FrameData}
	LHLD	CurrentFrameDataBytes
	XCHG			;DE ← CurrentFrameDataBytes
	LHLD	GetDataByteCount
	DAD	D		;HL ← total bytes this IOCB
	SHLD	GetDataByteCount
	LHLD	CurrentFrameToSendPtr
	SHLD	PrevFrameToSendPtr	;*** FOR DEBUGGING
	DB	opLXID	;DE ← CurrentFrameDataBytes
CurrentFrameDataBytes:
	DW	0
	DAD	D		;HL + CurrentFrameDataBytes
	LXI	D,FrameData
	DAD	D		;HL ← address of next character

{If the following calculation is negative, we need to start a new frame at the beginning of the RxFifo: LastNewFrameAddress - NextFrameToSendPtr}
	DB	opMVIA	;A ← LastNewFrameAddressLow
LastNewFrameAddressLow1:
	DB	0
	SUB	L
	DB	opMVIA	;A ← LastNewFrameAddressHigh
LastNewFrameAddressHigh1:
	DB	0
	SBB	H
	JP	UpdateFrameToSend

{Come here if the next frame starts at the beginning of the RxFifo. The Fifo State changes from Closed to Open. Disable interrupts for as short a time as possible.}
	LHLD	FirstFrameToFillPtr
	MVI	A,opJNZ		;Set FifoState to Open
	DI
	STA	FifoStateSwitch
	STA	RxFifoState
UpdateFrameToSend:
	SHLD	CurrentFrameToSendPtr
	EI
	LDA	FlowControlFlag		;IF ~FlowControl
	ORA	A
	JNZ	TestLastBuffer
	SHLD	CurrentFrameToSendPtr1	;Then save CFTS↑
	SHLD	CurrentFrameToSendPtr2
	JMP	FrameSentSwitch-1
TestLastBuffer:
	DI
	LHLD	CurrentFrameToFillPtr	;Else {SetLasBufferFlag
	CALL	SetLastBufferFlag
	EI
	LDA	LastBufferFlag
	ORA	A
	JNZ	FrameSentSwitch-1	;     IF ~LastBuffer & (SendingXoff | XoffSent)
	
TestXoffSent:
	LDA	FCState
	ANI	SendingXoff+XoffSent
	JZ	FrameSentSwitch-1
	DI
	CALL	OutputXon			;     THEN Send Xon Char}
	EI

{If we have sent a block of data for Bisync mode, return to Bisync processing.}
	DB	opJMP
FrameSentSwitch:
	DW	ReturnGetIocb
;	JMP	ReturnGetIocb
;	JMP	BlockSent
;
ReturnGetIocb:
{ Copy the IOCB back to the CP. Reset the Full flag so we will wait for the next IOCB. Notify the CP that we have completed this IOCB.}
	LDA	PortBusyFlag
	ORA	A
	JZ	SendGetIocb

	LXI	H,ReturnGetIocb
	JMP	SaveGetResumeAddressAndYield

SendGetIocb:
	STA	AbortGetFlag		;Reset AbortGetFlag
	LXI	H,GetIocbPcb
	CALL	WriteCPBuffer		;Send IOCB back to CP
	LXI	H,ResetGetFlag
	CALL	WriteCPBuffer
	LHLD	RS232CTaskWakeMask
	CALL	DoNakedNotify
	LXI	H,FetchGetIocb

SaveGetResumeAddressAndYield:
	SHLD	GetTaskResumeAddress
GetTaskYield:
{Continue to next task. RS232CGet is the last task in Domino.cfg, so we jump back to the top of round-robin task management.}
	JMP	MainLoop

	END