{	File: [Iris]<WMicro>DLion>RS232CInterrupts.asm
Modification History:
  Dennis Grundler:  1-Sep-84 17:10:49  Add copyright notice  
  Mike Thatcher: 29-Aug-84 22:49:50  fix to output Xon/Xoff after receiving an Xoff  
  Mike Thatcher: 23-Aug-84 18:14:10  fixed TxBufferPointer check after Xon
  Mike Thatcher: 21-Aug-84 17:14:16  add EI in Xoff1 interrupt
  Mike Thatcher: 13-Aug-84  6:22:54  Fix for saving completion code in RxAsyncSpecialInt and CheckFifoBoundary
  Mike Thatcher: 27-Mar-84  9:49:41  Fix to flow control glitch  
  Mike Thatcher: 13-Jan-84 14:26:02 Incorporate Grundler's fixes for aborting when FifoOverflow and EXPort OverflowFlag.
  Mike Thatcher:  3-Jan-84 15:03:49 Added Async Flow Control
  Tom Chang : 17-May-83 14:44:40 Make sure only use 3 SIO interrupt bits
  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.
}

{ 	Copyright (C) 1982, 1983, 1984 by Xerox Corporation.  All rights reserved.}


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

	IMP	AsyncTimerCounter	;From SIOSubs
	IMP	AsyncTimerStoppedFlag	;From SIOSubs
	IMP	GetByteCount		;From RS232CGet
	IMP	ErrorReport		;From Common
	IMP	RxFIFO			;From Buffer
	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	FCState
	EXP	FifoStateSwitch
	EXP	FirstFrameToFillPtr
	EXP	FirstRxCharPtr
	EXP	FirstMaxFrameBoundary
	EXP	FlowControlFlag
	EXP	LastBufferFlag
	EXP	LastNewFrameAddressHigh
	EXP	LastNewFrameAddressLow
	EXP	LastTxBufferAddressHigh
	EXP	LastTxBufferAddressHigh1
	EXP	LastTxBufferAddressLow
	EXP	LastTxBufferAddressLow1
	EXP	MaxFrameSize
	EXP	NoMoreBytesToSend
	EXP	OutputXoff
	EXP	OutputXon
	EXP	OverflowFlag
	EXP	PutCompletedFlag
	EXP	PutFinishedSwitch
	EXP	RxAsyncDataInt
	EXP	RxAsyncExternalInt
	EXP	RxAsyncSpecialInt
	EXP	RxFifoState
	EXP	RxFifoEnd
	EXP	RxIllegalInt
	EXP	RxSdlcDataInt
	EXP	RxSdlcExternalInt
	EXP	RxSdlcSpecialInt
	EXP	SetLastBufferFlag
	EXP	StoreFrameHeader
	EXP	TxAsyncDataInt
	EXP	TxAsyncExternalInt
	EXP	TxBisyncExternalInt
	EXP	TxBufferPointer
	EXP	TxSdlcExternalInt
	EXP	SdlcFrameFlag
	EXP	SdlcRS232CInterrupt
	EXP	TerminateFrame
	EXP	TxIllegalInt
	EXP	XoffReceivedFlag
	EXP	XoffChar
	EXP	XonChar
;
{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
{added the following}
	ANI	0EH		; just allow rightful three bits, 5/17/83
	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.}
	DB	opMVIA			;A ← XoffReceivedFlag
XoffReceivedFlag:
	DB	0
	ORA	A			;IF XoffReceivedFlag
	JNZ	ResetTxInterrupt	;THEN stop sending data
	
TestTxEnd:
	LHLD	TxBufferPointer		;ELSE IF ~lastCharacter sent
	DCX	H
	LDA	LastTxBufferAddressLow
	CMP	L
	JNZ	SendNextAsyncCharacter

	LDA	LastTxBufferAddressHigh
	CMP	H
	JNZ	SendNextAsyncCharacter	;  THEN send next character

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

ResetTxInterrupt;
	LDA	FCState
	ORI	StoppedTx
	STA	FCState
	MVI	A,ResetTxIntPending
	OUT	TxCont
	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
	
{Check for flow control, if true look for receiving an xoff/xon character  and set appropriate flags.  If no xon/xoff charater then determine if high water mark has been reached in the buffer.  Because of the buffering scheme, the high water mark(HWM) is set to MaxFrameSize + 138.  This is not looked for until there is less then 2*MaxFrameSize left in the buffer.  The LastBufferFlag is set when this state has occured}
	MOV	L,A		;Temporary save character
	LDA	FlowControlFlag	;IF ~FlowControl
	ORA	A
	JZ	CheckClientSpace	;go check space left in client's buffer
	
	MOV	A,L		;ELSE restore received character
	DB	opLXIH		;H←XonChar, L←XoffChar
XoffChar:
	DB	0
XonChar:
	DB	0
	CMP	L		;   IF ~Xoff Char THEN
	JNZ	TestXon		;        go test for Xon
	DCX	B		;   ELSE
	STA	XoffReceivedFlag	;set XoffReceived Flag
	JMP	Out		;	return
	
TestXon:
	CMP	H		;IF ~Xon Char  THEN
	JNZ	TestSpaceLeft	;   test space left
	DCX	B		;ELSE
	XRA	A		;   reset XoffReceivedFlag
	STA	XoffReceivedFlag
	LDA	PutCompletedFlag
	ORA	A		;IF ~PutCompleted
	JNZ	Out
		
SendChar:
	LDA	FCState
	ANI	-StoppedTx-1
	STA	FCState
	JMP	TestTxEnd
		
TestSpaceLeft:
	PUSH	D
	CALL	CheckFifoBoundary
	POP	D
	
CheckClientSpace:		;ELSE Null
{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

Out:
	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

{Check if need to send Xon/Xoff}
	LDA	FlowControlFlag	;IF ~FlowControl
	ORA	A
	JZ	EndOfFrame
	PUSH	D		;Save completion code
	CALL	CheckFifoBoundary
	POP	D		;restore completion code
	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
{removed ORA	A, replaced by the following}
	ANI	0EH		; just allow rightful three bits, 5/17/83

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

	DB	opMVIA		;A ← FlowControlFlag
FlowControlFlag:
	DB	0
	ORA	A
	CNZ	SetLastBufferFlag

	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
	STA	RxFifoState
	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

	LDA 	FlowControlFlag
	ORA	A
	CNZ	SetLastBufferFlag

	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

	LDA 	FlowControlFlag
	ORA	A
	CNZ	SetLastBufferFlag
	
	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
{This is pre-Klamath 11.0g code
	XRA	A
	MOV	M,A		;Turn off CompletionCode
}
{Instead of simply clearing CompletionCode, which gets us into an infinite loop during aborts we will simply clear EndOfFrame and FifoOverflow. 4-Jan-84}
	MOV	A,M		;Load Old CompletionCode
	ANI	1		;Mask off all but abort
	MOV	M,A		;Restore CompletionCode
	XRA	A		;Clear the SdlcFrameFlag
		
	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	0FFH	;Initialized to TRUE since no PUTS in progress
	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

;
SetLastBufferFlag:
{This subroutine which sets or clears the Last buffer flag assumes that the CurrentFrameToFillPtr (CFTF↑) is in the HL registers and the RxFifoState instruction has been set correctly for when the Fifo is open or Closed. (See description of the Get Fifo processing ).  The routine implements the following algorithm for setting/clearing the LastBufferFlag.

	IF (CurrentFrameToSendPtr<=(CurrentFrameToFillPtr+(2*MaxFrameSize)) AND fifoState = Closed)
	  OR (CurrentFrameToSendPtr<FirstFrameBoundary+138 AND 
	     RxFifoEnd<=(CurrentFrameToFillPtr+(2*MaxFrameSize)) AND fifoState = Open)
	THEN LastBufferFlag ← TRUE
	ELSE LastBufferFlag ← FALSE;
	
}
	XCHG
	LHLD	MaxFrameSize
	DAD	H
	DAD	D		;HL ← CFTF + 2*MaxFrameSize
	XRA	A
RxFifoState:
	DB	opJNZ		;determine if Open or Closed
	DW	CloseState
OpenState:
	DB	opLXID		;D ← RxFifoEnd
RxFifoEnd:
	DW	0
	MOV	A,L		;If HL-RxfifoEnd<0 THEN clearFlag
	SUB	E
	MOV	A,H
	SBB	D
	JM	ClearFlag
	LHLD	FirstMaxFrameBoundary
	LXI	D,138		;If (FirstMaxFrameBoundary+138
	DAD	D		;    - CurrentFrameToSendPt) < 0
				;THEN clear LastBufferFlag

CloseState:
	XCHG
	LHLD	CurrentFrameToSendPtr
	MOV	A,E		;IF (DE-CurrentFrameToSendPtr)<0
	SUB	L
	MOV	A,D
	SBB	H
	JM	ClearFlag	;THEN clear LastBufferFlag
	CMA			;ELSE set LastBufferFlag
	STA	LastBufferFlag
	RET
	
ClearFlag:
	XRA	A
	STA	LastBufferFlag
	RET
;
CheckFifoBoundary:
	DB	opMVIA		;A ← LastBufferFlag
LastBufferFlag:
	DB	0	;IF LastBufferFlag
	ORA	A
	RZ	
	LDA	FCState		;AND ~(sendingXoff | XoffSent) 
	ANI	SendingXoff+XoffSent
	RNZ	
	LHLD	CurrentFrameToSendPtr	;THEN IF ( BC<cfts<=(BC+HWM) )
	MOV	A,L
	SUB	C
	MOV	L,A
	MOV	A,H
	SBB	B			;note effect below
	MOV	H,A
	XCHG
	LHLD	HighWaterMark
	JP	ClosedFifoState		;From SBB instruction above
OpenFifoState:				;OR IF (BC<RXFIFOEnd<=(BC+HWM) )
	DAD	B
	XCHG
	LHLD	RxFifoEnd
	XCHG
	
ClosedFifoState:
	MOV	A,L
	SUB	E
	MOV	A,H
	SBB	D
	RM				;   THEN send XoffChar

{The following are the routines are called to send an Xon or Xoff char.  Because they modify interrupt vector addresses and the FCState variable, interrupts must be disabled before calling these routines.  

CAUTION:  These routines make reference to absolute address 2060H, which is the address of the AsyncJumpTable defined in Common.asm. }

OutputXoff:
	LDA	PutCompletedFlag	;IF ~PutCompleted
	ORA	A
	JNZ	NoTxOut
	LDA	FCState
	ANI	StoppedTx	;AND ~TxStopped
	LXI	H,Xoff1		;THEN set Interrupt handler to Xoff1
	JZ	SetXoffFlags
	LDA	FCState
	ORI	XoffSent
	STA	FCState
	LDA	XoffChar
	OUT	TxData
	RET

NoTxOut:
	LDA	XoffChar
	OUT	TxData		;ELSE send Xoff Character
	LXI	H,Xoff2		;     set Interrupt handler to Xoff2
SetXoffFlags:
	SHLD	2060H+1		;AsyncJumpTable+1
	LDA	FCState
	ORI	SendingXoff
	STA	FCState
	RET
;	
OutputXon:
	LXI	H,Xoff1	
	LDA	PutCompletedFlag	;IF PutCompleted
	ORA	A
	LDA	FCState
	MOV	E,A
	JZ	PutinProgress	; -- (from ORA instruction above)
	LXI	H,Xoff2
	ANI	SendingXoff	;  AND ~SendingXoff
	JNZ	XoffinProgress
	LDA	XonChar
	OUT	TxData		;THEN send Xon Character
	MOV	A,E
	ANI	-XoffSent-1	;   clear XoffSent Flag
;;;;;	ORI	SendingXon	;   set SendingXon Flag
	STA	FCState
	SHLD	2060H+1		;AsyncJumpTable+1
	RET
PutinProgress:
	MOV	A,E
	ANI	StoppedTx	;IF Xoff received and TX has stopped
	JNZ	DoIt		;THEN output Xon anyway
XoffinProgress:
	MOV	A,E		;ELSE set SendXon flag
	ORI	SendXon
	STA	FCState		;set Interrupt handler
	SHLD	2060H+1		;AsyncJumpTable+1
	RET
DoIt:	
	LDA	XonChar
	OUT	TxData		;THEN send Xon Character
	MOV	A,E
	ANI	-XoffSent-1	;   clear XoffSent Flag
	STA	FCState
	RET

;
{The following routines are interrupt handlesrs for the flow control characters which have been or need to be sent.  The routine Xoff1  will be invoked if a transmission is already in progress when an Xon/Xoff character needs to be sent.  Xoff2 is used when no transmission is currently going on so that after transmitting the character, the TxAsyncData interrupt will be reset.}
Xoff1:
	LDA	FCState
	MOV	L,A
	ANI	SendingXoff+XoffSent	;IF SendingXoff AND ~XoffSent
	CPI	SendingXoff
	JNZ	TestforSendXon
	LDA	XoffChar		;THEN send Xoff and return
	OUT	TxData
	MOV	A,L
	ORI	XoffSent
	STA	FCState
	JMP	ReturnXoff1
TestforSendXon:
	MOV	A,L			;ELSE IF SendXon
	ANI	SendXon
	JZ	ResetFlags
	LDA	XonChar
	OUT	TxData			;THEN send Xon
	MOV	A,L
	ANI	-(SendXon+SendingXoff+XoffSent)-1
	STA	FCState
	LXI	H,TxAsyncDataInt	; set interrupt vector to normal
	SHLD	2060H+1		;AsyncJumpTable+1
ReturnXoff1:
	POP 	H
	POP	PSW
	EI
	RET
	
ResetFlags:
	MOV	A,L			;ELSE reset flags
	ANI	-SendingXoff-1
;;;;;	ORI	XoffSent
	STA	FCState			;  set interrupt vector to normal
	LXI	H,TxAsyncDataInt
	SHLD	2060H+1		;AsyncJumpTable+1
	PCHL				;  goto transmit next character
;
Xoff2:
	DB	opMVIA		;A ← FCState
FCState:
	DB	0
	MOV	L,A
	ANI	SendXon		;IF SendXon
	JZ	CheckSendXoff
	LDA	XonChar
	OUT	TxData		;THEN send Xon
	MOV	A,L
	ANI	-(SendXon+SendingXoff+XoffSent)-1
;;;;;	ORI	SendingXon
	STA	FCState
	JMP	ReturnXoff2
CheckSendXoff:
	MOV	A,L		;ELSE IF SendingXoff
	ANI	SendingXoff
	JZ	Clear
	MOV	A,L
	ANI	-SendingXoff-1	;THEN clear SendingXoff
	ORI	XoffSent
;;;;;	JMP	TestforPut
TestforPut:
	STA	FCState
Clear:
	LXI	H,TxAsyncDataInt;set interrupt vector to normal
	SHLD	2060H+1		;AsyncJumpTable+1
	LDA	PutCompletedFlag
	ORA	A		;IF ~PutCompleted THEN
	JNZ	ResetTx
	PCHL			;  goto transmit next character
ResetTx:
	MVI	A,ResetTxIntPending
	OUT	TxCont		;ELSE clear interrrupt pending
ReturnXoff2:
	POP	H
	POP	PSW
	EI
	RET

	END