{	File: RS232CInterrupts.asm
Modification History:
  Chuck Fay : 17-Nov-82 18:10:24:  Fix FifoOverflow handling; move all stack POPs
    before EIs to avoid potential stack overflow memory smash; change RxIllegalInt
    and TxIllegalInt to press on rather than crash with MP 515.
  Jim Frandeen :  September 17, 1982  8:51 AM:  Allow any block size in Async mode.
  Jim Frandeen :  May 25, 1982  3:56 PM
  Jim Frandeen :  April 13, 1982  11:32 AM:  Created file.
}

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

	IMP	AsyncTimerCounter	;From SIOSubs
	IMP	AsyncTimerStoppedFlag	;From SIOSubs
	IMP	GetByteCount	;From RS232CGet
	IMP	ErrorReport		;From Common
	IMP	RxMaxFrameSize		;From BisyncInput
	IMP	StatusChangeFlag	;From RS232CGet
	IMP	TxBisyncStateSwitch	;From BisyncInterrupts
	IMP	TxUnderrunDetectedFlag	;From RS232CPut

	EXP	AsyncRS232CInterrupt
	EXP	AsyncTimerValue
	EXP	CharactersLeftInClientBuffer
	EXP	CharactersUntilBoundary
	EXP	CharLengthMask
	EXP	CharLengthMask1
	EXP	PrevAsyncDCD
	EXP	PrevCTS
	EXP	PrevSdlcDCD
	EXP	CurrentFrameToFillPtr
	EXP	CurrentFrameToSendPtr
	EXP	EndOfFrame
	EXP	ExitInterrupt
	EXP	FifoStateSwitch
	EXP	FirstFrameToFillPtr
	EXP	FirstRxCharPtr
	EXP	FirstMaxFrameBoundary
	EXP	LastNewFrameAddressHigh
	EXP	LastNewFrameAddressLow
	EXP	LastTxBufferAddressHigh
	EXP	LastTxBufferAddressHigh1
	EXP	LastTxBufferAddressLow
	EXP	LastTxBufferAddressLow1
	EXP	MaxFrameSize
	EXP	NoMoreBytesToSend
	EXP	PutCompletedFlag
	EXP	PutFinishedSwitch
	EXP	RxAsyncDataInt
	EXP	RxAsyncExternalInt
	EXP	RxAsyncSpecialInt
	EXP	RxIllegalInt
	EXP	RxSdlcDataInt
	EXP	RxSdlcExternalInt
	EXP	RxSdlcSpecialInt
	EXP	StoreFrameHeader
	EXP	TxAsyncDataInt
	EXP	TxAsyncExternalInt
	EXP	TxBisyncExternalInt
	EXP	TxBufferPointer
	EXP	TxSdlcExternalInt
	EXP	SdlcFrameFlag
	EXP	SdlcRS232CInterrupt
	EXP	TerminateFrame
	EXP	TxIllegalInt
;
{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
;
AsyncRS232CInterrupt:
{ Come here in Async mode from Common when we get a RST 6.5 Interrupt from the Zilog SIO chip. Save state }
	PUSH	PSW		
	PUSH	H

{Read the interruptVector to determine the cause of the interrupt. Multiply the vector by 2. This gives us a number which we can OR into the low bits of the jump table address. The jump table is in the Common so that we can assure its exact address and guarantee that the low 5 bits of the address are zero. }
	MVI	A,PointToWR2
	OUT	TxCont	
	IN	TxCont		; Read Interrupt Vector
	RLC			;Interupt Vector * 2
	ORI	60H		;Low address is 60H
	MOV	L,A
	MVI	H,20H		;High address is 20H
	PCHL			;Jump to JumpTable

{AsyncJumpTable: @2060H
	JMP	TxAsyncDataInt	; When Vecter =0 Tx Buffer Empty
	DB	0
	JMP	TxAsyncExternalInt	; When Vecter =2 Ex Stat
	DB	0
	JMP	TxIllegalInt	; When Vecter =4 Rx Char
	DB	0
	JMP	ExitInterrupt	; When Vecter =6 Sp Rx Cond
	DB	0
	JMP	RxIllegalInt	; When Vecter =8 Tx Buffer Empty
	DB	0
	JMP	RxAsyncExternalInt	; When Vecter =A Ex Stat
	DB	0
	JMP	RxAsyncDataInt	; When Vecter =C Rx Char
	DB	0
	JMP	RxAsyncSpecialInt	; When Vecter =E Sp Rx Cond
}

{The following interrupts should never happen, but occasionally they do, perhaps because of crosstalk on the DLion RS232 cable.  Therefore, we just ignore them, rather than crash with an MP code.  The one that seems to happen is RxIllegalInt.  The counters are for debugging use, for instance of new cables.}

RxIllegalInt:
	LXI	H,RxIllegalIntCount
	INR	M			;Increment illegal interrupt counter
	JMP	ExitInterrupt
TxIllegalInt:
	LXI	H,TxIllegalIntCount
	INR	M			;Increment illegal interrupt counter
	JMP	ExitInterrupt

RxIllegalIntCount:
	DB	0
TxIllegalIntCount:
	DB	0
;
TxAsyncDataInt:
{Come here when we get an interrupt for Tx Buffer Empty. See if the last character has already been sent.}
	LHLD	TxBufferPointer
	DCX	H
	LDA	LastTxBufferAddressLow
	CMP	L
	JNZ	SendNextAsyncCharacter

	LDA	LastTxBufferAddressHigh
	CMP	H
	JNZ	SendNextAsyncCharacter

{We already sent the last character.}
	MVI	A,ResetTxIntPending	
	OUT	TxCont
	STA	PutCompletedFlag
	POP	H
	POP	PSW
	EI			;Enable interrupts
	RET

SendNextAsyncCharacter:
	INX	H
	MOV	A,M		;Get byte from buffer
	OUT	TxData		;Send byte
	INX	H		;Point to next character
	SHLD	TxBufferPointer	;Update buffer pointer
	POP	H
	POP	PSW
	EI			;Enable interrupts
	RET
;
RxAsyncDataInt:
{ Come here when the SIO chip has another character of data ready. Reset the Async timer.}
	DB	opLXIH		;HL ← AsyncTimerValue
AsyncTimerValue:
	DW	0
	SHLD	AsyncTimerCounter	;Set iteration counter
	MVI	A,0B0H		;timer channel 2, mode 0, binary
	OUT	TimerMode
	LXI	H,TimerCount2+8000H
	MVI	M,0FEH		;Send low byte of 1840 decimal
	MVI	M,47H		;Send high byte
	XRA	A
	STA	AsyncTimerStoppedFlag

{Fetch the next character and mask it, depending on the length of the character. Store the character, increment the pointer to the next character position.}
	IN	RxData		;A ← RxData
	DB	opANI		;A ← RxData AND CharLengthMask
CharLengthMask:
	DB	0
	STAX	B		;Store character in Fifo
	INX	B

{See if we are have filled the Iocb.}
	DB	opLXIH		;HL ← CharactersUntilBoundary
CharactersLeftInClientBuffer:
	DW	0
	DCX	H		;Decrement count
	MOV	A,H
	ORA	L
	JZ	ClientBufferFull
	SHLD	CharactersLeftInClientBuffer

{See if we are at the end of a block.}
	DB	opLXIH		;HL ← CharactersUntilBoundary
CharactersUntilBoundary:
	DW	0
	DCX	H		;Decrement boundary count
	MOV	A,H
	ORA	L
	JZ	BlockFull
	SHLD	CharactersUntilBoundary

	POP	H
	POP	PSW
	EI			;Enable interrupts
	RET

ClientBufferFull:
{Come here if this character has filled the client's buffer. End this frame, mark it EOF, and start a new frame. This is a normal completion for Asynchronous mode. In asynchronous mode, a frame ends either when it fills the IOCB or when we get a timeout, whichever comes first.}
	PUSH	D		;Save DE
	MVI	E,80H		;Set EndOfFrame 
	JMP	TerminateFrame

BlockFull:
{Come here if this character has filled the block. This happens when a client's buffer is larger than our input buffer. End this frame, mark it end of block, and start a new frame. The Get routine will send this data to the CP.}
	PUSH	D		;Save DE
	MVI	E,4		;Set EndOfBlock 
	JMP	TerminateBlock
;
RxAsyncExternalInt:
{ Come here when we get an External/Status interrupt. Register RR0 indicates the cause of the interrupt:

80: 10000000	Break Asynchronous
08: 00001000	Data Carrier Detect (DCD)}

	IN	RxCont	;Read RR0
	MOV	H,A		;Save RR0 in H
	ANI	DCD
	DB	opCPI		;Compare to previous DCD
PrevAsyncDCD:
	DB	0
	STA	PrevAsyncDCD	;Save current DCD
	MVI	A,ResetExternalStatusInterrupts
	OUT	RxCont
	JZ	TestRxAsyncBreak

{Continue if DataCarrierDetect has changed state. Notify the Get loop so that it will do a NakedNotify. We store ResetExternalStatusInterrupts in StatusChangeFlag.}
	STA	StatusChangeFlag

TestRxAsyncBreak:
	ORA	H		;RR0 will be negative if Abort bit is set
	JM	AsyncBreakAbortDetected		

	POP	H
	POP	PSW
	EI			;Exit Interrupt
	RET

AsyncBreakAbortDetected:
{We got a Break/Abort. We end the current frame and return it to the CP with aborted status in the Iocb. Set FrameCompletionCode to Aborted. NOTE: If this frame does not contain any characters, we will not send any. We check for this at StartFrameTransfer.}
	PUSH	D		;Save DE
	MVI	E,1		;Set CompletionCode to Aborted

TerminateFrame:
{We get called from Get when we get a timeout or an abort. E = CompletionCode: 1 for Aborted or 80H for Timeout. End this frame and start a new one.}
	LHLD	GetByteCount
	SHLD	CharactersLeftInClientBuffer

TerminateBlock:
{RxMaxFrameSize is the number of characters until our next boundary.}
	LHLD	RxMaxFrameSize
	SHLD	CharactersUntilBoundary
	JMP	EndOfFrame
;
RxAsyncSpecialInt:
{ Come here when we get a Special interrupt. Register RR1 indicates the cause of the interrupt:

40: 01000000	Framing error in Async
20: 00100000	RxOverrun
10: 00010000	Parity error

A Framing Error occurs if the character is assembled without any stop bits. This bit is set only for the character on which it occurred.

RxOverrun means data is being overwritten because the channel's three-byte receiver buffer is full and a new character is being received.

A Parity Error occurs when the parity bit of the character does not match with the programmed parity. Once this bit is set, it remains set until the Error Reset Command is given.}

	PUSH	D		;Save DE
{Read the character that caused the interrupt and place it in the RxFifo as we would a normal character.}
	IN	RxData		;A ← RxData
	DB	opANI		;A ← RxData AND CharLengthMask
CharLengthMask1:
	DB	0
	STAX	B		;Store character in Fifo
	INX	B
	LXI	H,8000H+RxCont	;Point to RxCont register
	MVI	M,PointToWR1
	MOV	A,M		;Read CompletionCode from RR1
	ANI	0F0H
	MOV	E,A		;Save CompletionCode in E
	MVI	M,ErrorReset

{RxMaxFrameSize is the number of characters until our next boundary.}
	LHLD	RxMaxFrameSize
	SHLD	CharactersUntilBoundary

	JMP	EndOfFrame
;
SdlcRS232CInterrupt:
{ Come here in Sdlc mode from the EXEC when we get a RST 6.5 Interrupt from the Zilog SIO chip. Save state }
	PUSH	PSW		
	PUSH	H

{Read the interruptVector to determine the cause of the interrupt. Multiply the interrupt vector by 2. This gives us a number which we can OR into the low bits of the jump table address. The jump table is in the Common so that we can assure its exact address and guarantee that the low 5 bits of the address are zero.}
	MVI	A,PointToWR2
	OUT	TxCont	
	IN	TxCont		; Read Interrupt Vector
	ORA	A

PutFinishedSwitch:
{This opcode will be changed to a JMP when the last character has been sent. The jump table will then pass control to NoMoreBytesToSend.}
	DB	opJNZ
	DW	BranchOnSdlcInterruptVector

TxSdlcDataInt:
{Come here when we get an interrupt for Tx Buffer Empty. Send another character to the SIO chip.}
	DB	opLXIH		;HL ← TxBufferPointer
TxBufferPointer:
	DW	0

{We want to send the character and then test for last so as to service the interrupt as fast as possible.}
	MOV	A,M		; get byte from buffer
	OUT	TxData		; send byte
	DB	opMVIA		;A ← LastTxBufferAddressLow
LastTxBufferAddressLow:
	DB	0
	CMP	L
	JZ	LastTxBufferAddressLowEqual

	INX	H		; increment buffer pointer
	SHLD	TxBufferPointer	; update buffer pointer

ExitInterrupt:
{Come here to restore state and exit. }
	POP	H
	POP	PSW
	EI			;Enable interrupts
	RET

LastTxBufferAddressLowEqual:
	DB	opMVIA		;A ← LastTxBufferAddressHigh
LastTxBufferAddressHigh:
	DB	0
	CMP	H
	JZ	LastByteToSend

	INX	H		; increment buffer pointer
	SHLD	TxBufferPointer	; update buffer pointer
	POP	H
	POP	PSW
	EI			;Enable interrupts
	RET

LastByteToSend:
{We have just sent the last byte. The next time we get a TxData interrupt, we will JMP to NoMoreBytesToSend.}
	MVI	A,opJMP
	STA	PutFinishedSwitch
	STA	PutCompletedFlag
	POP	H
	POP	PSW
	EI			;Enable interrupts
	RET

NoMoreBytesToSend:
	MVI	A,ResetTxIntPending	
	OUT	TxCont	; disable this interrupt & exit

	POP	H
	POP	PSW
	EI			;Enable interrupts
	RET
;

BranchOnSdlcInterruptVector:
	RLC			;Interupt Vector * 2
	MVI	H,20H		;High address is 20H
	ORA	H		;Low address is 20H
	MOV	L,A
	PCHL			;Jump to JumpTable

{SdlcJumpTable: @2060H
	JMP	NoMoreBytesToSend	; When Vecter =0 Tx Buffer Empty
	DB	0
	JMP	TxSdlcExternalInt	; When Vecter =2 Ex Stat
	DB	0
	JMP	TxIllegalInt	; When Vecter =4 Rx Char
	DB	0
	JMP	ExitInterrupt	; When Vecter =6 Sp Rx Cond
	DB	0
	JMP	RxIllegalInt	; When Vecter =8 Tx Buffer Empty
	DB	0
	JMP	RxSdlcExternalInt	; When Vecter =A Ex Stat
	DB	0
	JMP	RxSdlcDataInt	; When Vecter =C Rx Char
	DB	0
	JMP	RxSdlcSpecialInt	; When Vecter =E Sp Rx Cond
}
;
TxSdlcExternalInt:
TxAsyncExternalInt:
TxBisyncExternalInt:
{Tx Sdlc External Interrupt processing routine
Come here if we get Break, Clear To Send, Data Carrier Detect, or Tx Underrun. Notify the Sdlc Put Loop if we get underrun.}
	IN	TxCont
	MOV	H,A		;Save RR0 in H
	ANI	TxUnderrun
	STA	TxUnderrunDetectedFlag

{Test for change in Clear To Send.}
	MOV	A,H
	ANI	CTS
	DB	opCPI		;Compare to previous CTS
PrevCTS:
	DB	0
	STA	PrevCTS		;Save current CTS
	MVI	A,ResetExtStatusInterrupts	
	OUT	TxCont
	JZ	NoChangeInCTS

	STA	StatusChangeFlag

NoChangeInCTS:
	POP	H
	POP	PSW
	EI			;Enable interrupts
	RET
;
RxSdlcDataInt:
{Well here it is folks! This is what we have all been waiting for. This is why Domino never uses registers B and C. They are reserved for NextRxCharPtr so that SDLC will run at 56KB in interrupt mode.

Fetch the next character and store the character, increment the pointer to the next character position. Note that we do not check for any boundary. We depend on the EndOfFrame interrupt to stop the receiver. At 56KB, we don't have time to check the boundary.}

{Set SdlcFrameFlag so we know we're in a frame if we get an abort. A = 18H from the InterruptVector*2. This seems to waste 13 cycles, but Abort is the longest interrupt, and it will cause Overruns if we try a longer method of finding out if we are in a frame.}
	STA	SdlcFrameFlag
	IN	RxData		;A ← RxData
	STAX	B		;Store character in Fifo
	INX	B
	POP	H
	POP	PSW		; Restore Registers
	EI
	RET
;
RxSdlcExternalInt:
{ Come here when we get an External/Status interrupt. Register RR0 indicates the cause of the interrupt:

80: 10000000	Break
08: 00001000	Data Carrier Detect (DCD)}

	IN	RxCont		;Read RR0
	MOV	H,A		;Save RR0 in H
	ORA	H
	JP	TestSdlcStateChange

{We got a Break/Abort. Check to see if we are processing a frame. Breaks detected when the SIO is not synchronized to the data stream are not interesting.}
	DB	opMVIA		;A ← SdlcFrameFlag
SdlcFrameFlag:
	DB	0
	ORA	A
	JZ	TestSdlcStateChange

{If we are in a frame, we end the current frame and return it to the CP with aborted status in the Iocb. Set FrameCompletionCode to Aborted. NOTE: If this frame does not contain any characters, we will not send any. We check for this at StartFrameTransfer.}
	PUSH	D		;Save DE
	MVI	E,1		;Set CompletionCode to Aborted
	JMP	EndOfFrame

TestSdlcStateChange:
	MOV	A,H		;A ← RR0
	ANI	DCD
	DB	opCPI		;Compare to previous DCD
PrevSdlcDCD:
	DB	0
	STA	PrevSdlcDCD	;Save current DCD
	MVI	A,ResetExternalStatusInterrupts
	OUT	RxCont
	JZ	ExitSdlcExternalInt

{DataCarrierDetect has changed state. Notify the Get loop so that it will do a NakedNotify. We store ResetExternalStatusInterrupts in StatusChangeFlag.}
	STA	StatusChangeFlag

ExitSdlcExternalInt:
	POP	H
	POP	PSW
	EI			;Exit Interrupt
	RET
;
RxSdlcSpecialInt:
{ Come here when we get a Special interrupt. Register RR1 indicates the cause of the interrupt:

80: 10000000	End Of Frame 
40: 01000000	CRC error
20: 00100000	RxOverrun
10: 00010000	Parity error

RxOverrun means data is being overwritten because the channel's three-byte receiver buffer is full and a new character is being received.

A Parity Error occurs when the parity bit of the character does not match with the programmed parity. Once this bit is set, it remains set until the Error Reset Command is given.}

	PUSH	D		;Save DE

{Read the character that caused the interrupt and place it in the RxFifo as we would a normal character.}
	IN	RxData		;A ← RxData
	STAX	B		;Store character in Fifo
	INX	B
	LXI	H,8000H+RxCont	;Point to RxCont register
	MVI	M,PointToWR1
	MOV	A,M		;Read CompletionCode from RR1
	ANI	0F0H		;Mask off CRC residue
	MOV	E,A		;Save CompletionCode in E
	MVI	M,ErrorReset

;
{This is the Get Fifo processing system. 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.}

{The Fifo has two states: Open and Closed. The Fifo is in the Open state when its boundary is the end of the Fifo. The Fifo is in the Closed state when its boundary is the next frame to send. In the Closed state, we have to be careful not to run into the next frame to send. It is in the Closed state that we are in danger of overflow. An overflow condition occurs when there is no room for a maximum size frame.

The Fifo starts in the Open state. We pass from the Open state to the Closed state when the interrupt routine wraps around and begins a new frame to fill at the beginning of the Fifo. We pass from the Closed state to the Open state when the Get routine wraps around and begins a new frame to send at the beginning of the Fifo.}


EndOfFrame:
{Come here when a frame has terminated. E =  CompletionCode. BC points to the last character stored + 1. We have satisfied the Rx interrupt condition. Since this is EndOfFrame, we won't have to worry about another input character for a while, but we do have to worry about the transmit buffer becoming empty. This is a good time to check for it.}
	MVI	A,PointToWR2
	OUT	TxCont	
	IN	TxCont		; Read Interrupt Vector
	ORA	A
	JZ	HandleTxDataInEOF

StoreFrameHeader:
{Enter here for Bisync end of frame.}
	DB	opLXIH		;HL ← CurrentFrameToFillPtr
CurrentFrameToFillPtr:
	DW	0		;HL points to CompletionCode

{The CompletionCode is in E.  OR this with the fifo OverflowFlag in case this frame already had a fifo overflow.}
	DB	opMVIA		;A ← OverflowFlag
OverflowFlag:
	DB	0		;0 => no overflow, 2 => overflow
	ORA	E
	MOV	M,A		;Store CompletionCode
	XRA	A		;WARNING! Note side effect on FifoStateSwitch below
	STA	OverflowFlag	;Turn off fifo overflow flag for next time
	INX	H		;Point to count field of Frame
	MOV	M,C		;Store low pointer to last char
	INX	H		;Point to count field high of frame
	MOV	M,B		;Store high pointer to last char

{Now initialize the NextFrameToFill depending on the state of the Fifo: Open or Closed. The following op code will be JMP if the Fifo State is Closed, or JNZ if the Fifo State is Open. Because of the XRA instruction above, the ConditionCode will always be = 0 when we come here. When the Fifo wraps around in the Get routine, we set the Switch to opJNZ for Open. When the Fifo wraps around in the interrupt routine, we set the Switch to opJMP for Closed.}
FifoStateSwitch:
	DB	opJNZ
	DW	FifoClosed

FifoOpen:
{JNZ will always fall through when the State is Open. Come here if the next frame to send is behind us. This means we have until the end of the Fifo for another frame. See if we have enough room in the Fifo to start another frame. If the following calculation is negative, we have to start the next frame at the beginning of the Fifo: LastNewFrameAddress - NextFrameToFillPtr.}

	DB	opMVIA		;A ← LastNewFrameAddressLow
LastNewFrameAddressLow:
	DB	0
	SUB	C
	DB	opMVIA		;A ← LastNewFrameAddressHigh
LastNewFrameAddressHigh:
	DB	0
	SBB	B
	JM	StartFirstAsyncFrame

{Continue if we have room for another frame. Now we can initialize CurrentFrameToFillPtr and BC (NextRxCharPtr). Set HL to point to new frame.}
	MOV	D,B
	MOV	E,C		;DE ← CurrentFrameToFillPtr
	LXI	H,FrameData
	DAD	D		;HL ← new NextRxCharPtr
	XCHG			;HL ← CurrentFrameToFillPtr
	MOV	B,D
	MOV	C,E		;BC ← NextRxCharPtr
	MVI	M,FrameValid	;Set FrameValid
	INX	H
	SHLD	CurrentFrameToFillPtr
	XRA	A
	MOV	M,A		;Turn off CompletionCode
	STA	SdlcFrameFlag

	POP	D		;Restore DE
	POP	H
	POP	PSW
	EI			;Enable interrupts
	RET

StartFirstAsyncFrame:
{Come here to begin a new frame at the beginning of the Fifo. First, be sure we have room to start a new frame at the beginning of the Fifo. We calculate: CurrentFrameToSendPtr - (FirstFrameToFillPtr + MaxFrameSize). If this is negative, we have an overflow condition.}
	LHLD	CurrentFrameToSendPtr
	DB	opLXID		;DE ← FirstMaxFrameBoundary
FirstMaxFrameBoundary:
	DW	0
	MOV	A,L
	SBB	E
	MOV	A,H
	SBB	D
	JM	FifoOverflow

{Continue if we have room to start a new frame. Now we can initialize the FifoState to Closed.}
	MVI	A,opJMP
	STA	FifoStateSwitch
	DB	opLXIB		;BC ← FirstRxCharPtr
FirstRxCharPtr:
	DW	0
	DB	opLXIH		;HL ← FirstFrameToFillPtr
FirstFrameToFillPtr:
	DW	0
	MVI	M,FrameValid	;Set FrameValid
	INX	H
	SHLD	CurrentFrameToFillPtr
	XRA	A
	MOV	M,A		;Turn off CompletionCode
	STA	SdlcFrameFlag

	POP	D
	POP	H
	POP	PSW
	EI			;Enable interrupts
	RET

FifoClosed:
{BC = NextFrameToFillPtr. Test to see that we do not overflow the Fifo. We calculate: CurrentFrameToSendPtr - (NextFrameToFillPtr + MaxFrameSize) If this is negative, we have an overflow condition. MaxFrameSize is the size specified in the client's IOCB + FrameData.}
	DB	opLXID		;DE ← CurrentFrameToSendPtr
CurrentFrameToSendPtr:
	DW	0
	DB	opLXIH		;DE ← MaxFrameSize
MaxFrameSize:
	DW	0
	DAD	B		;HL ← MaxFrameSize+NextFrameToFillPtr
	MOV	A,E
	SUB	L		;Subtract from CurrentFrameToSendPtr
	MOV	A,D
	SBB	H
	JM	FifoOverflow

{Continue if we have room to start a new frame. Now we can initialize CurrentFrameToFillPtr.}
	MOV	D,B
	MOV	E,C		;DE ← CurrentFrameToFillPtr
	LXI	H,FrameData
	DAD	D		;HL ← new NextRxCharPtr
	XCHG			;HL ← CurrentFrameToFillPtr
	MOV	B,D
	MOV	C,E		;BC ← NextRxCharPtr
	MVI	M,FrameValid	;Set FrameValid
	INX	H
	SHLD	CurrentFrameToFillPtr
	XRA	A
	MOV	M,A		;Turn off CompletionCode
	STA	SdlcFrameFlag

	POP	D
	POP	H
	POP	PSW
	EI			;Enable interrupts
	RET
;
FifoOverflow:
{We don't have room to start a new frame. We will store the next frame right over the current frame. Set CompletionCode back to empty so the GetLoop won't think this frame is ready. When the next frame completes, we will OR the CompletionCode with OverflowFlag to signal the loss of this frame to the GetLoop.}
	MVI	A,FifoOverflowed
	STA	OverflowFlag	;Note overflow for next time through EndOfFrame
	LHLD	CurrentFrameToFillPtr
	XRA	A
	MOV	M,A		;Turn off CompletionCode
	STA	SdlcFrameFlag
	LXI	D,FrameData-1
	DAD	D
	MOV	B,H
	MOV	C,L	
	POP	D
	POP	H
	POP	PSW
	EI			;Enable interrupts
	RET
;
HandleTxDataInEOF:
{See if the last character has been sent. If the last byte has already been sent, we don't have to worry about clearing the interrupt.}
 	DB	opMVIA	;A ← PutCompletedFlag
PutCompletedFlag:
	DB	0
	ORA	A
	JNZ	StoreFrameHeader

	LHLD	TxBufferPointer
	MOV	A,M		;Get character from buffer
	OUT	TxData		;send byte
	DB	opMVIA		;A ← LastTxBufferAddressLow
LastTxBufferAddressLow1:
	DB	0
	CMP	L
	JNZ	UpdateTxBufferPointer

	DB	opMVIA		;A ← LastTxBufferAddressHigh
LastTxBufferAddressHigh1:
	DB	0
	CMP	H
	JNZ	UpdateTxBufferPointer

{We have just sent the last byte. The next time we get a TxData interrupt, we will JMP to NoMoreBytesToSend.}
	MVI	A,opJMP
	STA	PutFinishedSwitch
	STA	PutCompletedFlag
	JMP	StoreFrameHeader

UpdateTxBufferPointer:
	INX	H		; increment buffer pointer
	SHLD	TxBufferPointer	; update buffer pointer
	JMP	StoreFrameHeader

	END