;===========================================================================
; File: PCSPAK.asm
; Project: MUNGA
; Contents: PC serial packet driver
;---------------------------------------------------------------------------
;   Date    Who  Modification
; --------  ---  -----------------------------------------------------------
; 01/16/95  CPB  Initial coding
;---------------------------------------------------------------------------
; Copyright (C) 1994, Virtual World Entertainment, Inc.
; All Rights reserved worldwide
; This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL
;===========================================================================

		include l4asm.inc
		include pcserial.inc
		include pcpic.inc
		include pcdpmi.inc

;===========================================================================
;; Diagnostics flags
;===========================================================================
KEEP_HISTORY equ 0
DIE_ON_ERROR equ 1
DIE_ON_OVERFLOW equ 1
WATCH_IRQ equ 1

THE_WAY_IT_WAS equ 0

;===========================================================================
; Equates
;===========================================================================
if DIE_ON_ERROR

	DISABLE_AND_DIE	MACRO val			;debugging macro: crash to debugger
			;--------------------------------------------------------------
			; Disable UART interrupt
			;--------------------------------------------------------------
			mov	dx,global_serial_port
			RETRACT_MCR	 UART_MCR_IRQ+UART_MCR_RTS
			; NOTE: from this point on, interrupts CANNOT occur from the UART.
			;--------------------------------------------------------------
			; Send non-specific EOI just in case we're in IRQ routine
			;--------------------------------------------------------------
			PIC_RESET_MASTER
			;--------------------------------------------------------------
			; Okay, NOW die.
			;--------------------------------------------------------------
			push	eax							;save ax
			push	edx							;save dx
			mov	edx,0FFFFFFFFh				;crash loudly
			mov	eax,val
			mov	[edx],eax
	ENDM

else
	DISABLE_AND_DIE	MACRO val
	ENDM

endif


if WATCH_IRQ

	IRQ_WATCH MACRO val
		mov	iThinkIRQIsOn,val
	ENDM

	IRQ_WATCH_ENTRY MACRO
		push	iThinkIRQIsOn
		mov	iThinkIRQIsOn,0
	ENDM

	IRQ_WATCH_EXIT MACRO
		pop	iThinkIRQIsOn
	ENDM

else

	IRQ_WATCH MACRO val
	ENDM

	IRQ_WATCH_ENTRY MACRO
	ENDM

	IRQ_WATCH_EXIT MACRO
	ENDM

endif

TICK    MACRO				;debugging macro: ticks the speaker

		push	dx
		push	ax

		mov	dx,061h
		in		al,dx

		xor	al,002h
		out	dx,al

		nop
		nop
		nop

		xor	al,002h
		out	dx,al

		pop	ax
		pop	dx
ENDM

SPKR_SET    MACRO				;debugging macro

		push	dx
		push	ax

		mov	dx,061h
		in		al,dx

		or		al,002h
		out	dx,al

		pop	ax
		pop	dx
ENDM

SPKR_RESET    MACRO			;debugging macro

		push	dx
		push	ax

		mov	dx,061h
		in		al,dx

		and	al,(NOT 002h)
		out	dx,al

		pop	ax
		pop	dx
ENDM

SPKR_TOGGLE    MACRO			;debugging macro

		push	dx
		push	ax

		mov	dx,061h
		in		al,dx

		xor	al,002h
		out	dx,al

		pop	ax
		pop	dx
ENDM

;-------------------------------------------------------------------------
; Command buffer equates
;
; WARNING**WARNING**WARNING**WARNING**WARNING**WARNING**WARNING**WARNING
;	  Any modifications to this structure MUST be
;	  reflected in L4PCSPAK.hpp!!
;
;	  BE ESPECIALLY CAREFUL IF 'BUFSIZE' IS CHANGED!
;
; WARNING**WARNING**WARNING**WARNING**WARNING**WARNING**WARNING**WARNING
;-------------------------------------------------------------------------
COMBUFSIZE	EQU	512	;;;1024					;buffer size (must be power of 2)
								;; was 256 before 7-13-95 cpb
COMBUF  STRUC
			head			dw	0
			tail			dw	0
			tempHead		dw	0
			tempTail		dw	0

			count			dw	0
			tempIn		dw	0
			tempOut		dw	0

			data			db	COMBUFSIZE dup (?)
COMBUF  ENDS

;------------------------------------------------
; COM_INIT macro
; Initializes COMBUF structure
;------------------------------------------------
; Enter:
;	esi   = pointer to object containing COMBUF
;	name  = offset to COMBUF within object
;------------------------------------------------
; Side effects:
;		(none)
;------------------------------------------------
COM_INIT MACRO name	;enter with object pointer in esi

			add	esi,name						;;build COMBUF pointer in esi

			mov	head[esi],0
			mov	tempHead[esi],0
			mov	tempTail[esi],0
			mov	tail[esi],0

			mov	count[esi],0
			mov	tempIn[esi],0
			mov	tempOut[esi],0

			sub	esi,name						;;restore esi
ENDM

;------------------------------------------------
; COM_PUT_AL macro
; 'Temporarily' puts character in AL into COMBUF
;------------------------------------------------
; Enter:
;		esi	= pointer to object containing COMBUF
;		name  = offset to COMBUF within object
;------------------------------------------------
; Side effects:
;		ebx modified
;------------------------------------------------
COM_PUT_AL MACRO name
			LOCAL	label1
			LOCAL	label2

			add	esi,name						;;build COMBUF pointer in esi

			cmp	tempIn[esi],COMBUFSIZE

if DIE_ON_OVERFLOW
			jb		label1
			DISABLE_AND_DIE 8
endif
			jge	label2
label1:

			inc	tempIn[esi]					;;increment 'tempIn' count

			xor	ebx,ebx						;;get 'tempHead' offset in ebx
			mov	bx,tempHead[esi]
			add	esi,ebx						;;add to base
			mov	data[esi],al				;;write the data
			sub	esi,ebx						;;restore COMBUF pointer

			inc	bx								;;bump 'tempHead'
			and	bx,COMBUFSIZE-1			;;limit it
			mov	tempHead[esi],bx			;;save the new 'tempHead' offset
label2:
			sub	esi,name						;;restore esi
ENDM

;------------------------------------------------
; COM_RESTORE_IN macro
; Discards characters 'temporarily' placed in COMBUF
;------------------------------------------------
; Enter:
;		esi	= pointer to object containing COMBUF
;		name  = offset to COMBUF within object
;------------------------------------------------
; Side effects:
;		bx modified
;------------------------------------------------
COM_RESTORE_IN MACRO name

			add	esi,name						;;build COMBUF pointer in ecx

			mov	tempIn[esi],0				;;clear the 'tempIn' count

			mov	bx,head[esi]				;;restore 'tempHead' index
			mov	tempHead[esi],bx

			sub	esi,name						;;restore esi
ENDM

;------------------------------------------------
; COM_ACCEPT_IN macro
; Accepts characters 'temporarily' placed in COMBUF
;------------------------------------------------
; Enter:
;		esi	= pointer to object containing COMBUF
;		name  = offset to COMBUF within object
;------------------------------------------------
; Side effects:
;		bx modified
;------------------------------------------------
COM_ACCEPT_IN MACRO name

			add	esi,name						;;build COMBUF pointer in ecx

			xor	bx,bx							;;prepare for exchange
			xchg	bx,tempIn[esi]				;;get, clear 'tempIn' count
			add	count[esi],bx				;;add to current count

			mov	bx,tempHead[esi]			;;set the new head offset
			mov	head[esi],bx

			sub	esi,name						;;restore esi
ENDM

;------------------------------------------------
; COM_GET_AL macro
; 'Temporarily' gets character into AL from COMBUF
;------------------------------------------------
; Enter:
;		esi	= pointer to object containing COMBUF
;		name  = offset to COMBUF within object
;------------------------------------------------
; Side effects:
;		ebx modified
;------------------------------------------------
COM_GET_AL MACRO name

			add	esi,name						;;build COMBUF pointer in ecx

			inc	tempOut[esi]				;;inc 'tempOut' count

			xor	ebx,ebx						;;get 'tempTail' offset in ebx
			mov	bx,tempTail[esi]

			add	esi,ebx						;;add to base
			mov	al,data[esi]				;;read the data
			sub	esi,ebx						;;restore COMBUF pointer

			inc	bx								;;bump 'tempTail'
			and	bx,COMBUFSIZE-1			;;limit it
			mov	tempTail[esi],bx			;;save the new 'tempTail' offset

			sub	esi,name						;;restore esi
ENDM

;------------------------------------------------
; COM_RESTORE_OUT macro
; Restores characters 'temporarily' removed from COMBUF
;------------------------------------------------
; Enter:
;		esi	= pointer to object containing COMBUF
;	name  = offset to COMBUF within object
;------------------------------------------------
; Side effects:
;		bx modified
;------------------------------------------------
COM_RESTORE_OUT MACRO name

			add	esi,name						;;build COMBUF pointer in ecx

			mov	tempOut[esi],0				;;clear the 'tempOut' count

			mov	bx,tail[esi]				;;restore 'tempTail' index
			mov	tempTail[esi],bx

			sub	esi,name						;;restore esi
ENDM

;------------------------------------------------
; COM_ACCEPT_OUT macro
; Discards characters 'temporarily' removed from COMBUF
;------------------------------------------------
; Enter:
;		esi	= pointer to object containing COMBUF
;		name  = offset to COMBUF within object
;------------------------------------------------
; Side effects:
;		bx modified
;------------------------------------------------
COM_ACCEPT_OUT MACRO name

			add	esi,name						;;build COMBUF pointer in ecx

			xor	ax,ax							;;prepare for exchange
			xchg	ax,tempOut[esi]			;;get, clear 'tempOut' count
			sub	count[esi],ax				;;subtract from current count

			mov	ax,tempTail[esi]			;;set the new tail offset
			mov	tail[esi],ax

			sub	esi,name						;;restore esi
ENDM

;------------------------------------------------
; COM_EAT_PACKET macro
; Discards entire buffer item from COMBUF
;------------------------------------------------
; Enter:
;		esi	= pointer to object containing COMBUF
;		ebx	= pointer to command length table
;	name	= offset to COMBUF within object
;------------------------------------------------
; Side effects:
;		ax modified
;------------------------------------------------
COM_EAT_PACKET MACRO name

			add	esi,name						;;build COMBUF pointer in esi

			push	ebx
			xor	ebx,ebx						;;get 'tail' offset in ebx
			mov	bx,tail[esi]
			add	esi,ebx						;;add to base
			mov	al,data[esi]				;;read the packet command byte
			sub	esi,ebx						;;restore COMBUF pointer
			pop	ebx

			and	al,07fh						;;remove the MSB
			xlatb									;;convert packet command to length
			xor	ah,ah							;;convert packet length to unsigned word
			inc	ax								;;include the command byte
			sub	count[esi],ax				;;subtract from count

			add	ax,tail[esi]				;;move the tail
			and	ax,COMBUFSIZE-1			;;limit it
			mov	tail[esi],ax				;;save it
			mov	tempTail[esi],ax			;;set the new tempTail index

			mov	tempOut[esi],0				;;clear the 'tempOut' count also
			
			sub	esi,name						;;restore esi
						
ENDM

;------------------------------------------------
; COM_SPACE_IN macro
; Returns input bytes available in COMBUF
;------------------------------------------------
; Enter:
;		esi	= pointer to object containing COMBUF
;		name  = offset to COMBUF within object
;		reg   = register to use
;------------------------------------------------
; Side effects:
;		ax contains number of bytes available
;------------------------------------------------
COM_SPACE_IN MACRO name,reg

			add	esi,name						;;build COMBUF pointer in ecx
			mov	reg,COMBUFSIZE
			sub	reg,count[esi]
			sub	reg,tempIn[esi]
			sub	esi,name						;;restore esi
ENDM

;------------------------------------------------
; COM_SPACE_OUT macro
; Returns output bytes available in COMBUF
;------------------------------------------------
; Enter:
;		esi	= pointer to object containing COMBUF
;	name  = offset to COMBUF within object
;------------------------------------------------
; Side effects:
;		ax contains number of bytes available
;------------------------------------------------
COM_SPACE_OUT MACRO name

			add	esi,name						;;build COMBUF pointer in ecx
			mov	ax,count[esi]
			sub	ax,tempOut[esi]
			sub	esi,name						;;restore esi
ENDM

;-------------------------------------------------------------------------
; IRQ data equates
;-------------------------------------------------------------------------

IRQDATA STRUC
			irqHandler		dd ?
			irqThis			dd ?

			irqOldOffset	dd ?		;THE ORDER OF THESE TWO IS CRITICAL.
			irqOldSelector	dw ?		;See the interrupt handlers near the end.

			irqPort			dw ?

			irqNum			db ?
			irqPICbit		db	?
			irqOldPICbits	db ?
			irqPadByte		db	?
IRQDATA ENDS
;-------------------------------------------------------------------------
; PCSPAK object equates
;
; WARNING**WARNING**WARNING**WARNING**WARNING**WARNING**WARNING**WARNING
;	  Any modifications to this structure MUST be
;	  reflected in PCSPAK.hh!!
; WARNING**WARNING**WARNING**WARNING**WARNING**WARNING**WARNING**WARNING
;-------------------------------------------------------------------------

TX_IDLE_STATE	   EQU	0
TX_RESTART_STATE	EQU	1
TX_BODY_STATE	   EQU	2
TX_WAIT_STATE	   EQU	3

RX_IDLE_STATE	   EQU	0
RX_ACTIVE_STATE	EQU	1

;
; Lower eight bits reserved for UART status
;
ERR_INIT				EQU	0000000100000000b
TX_EARLY_ERR		EQU	0000001000000000b
TX_ABANDON			EQU	0000010000000000b
TX_RESTART			EQU	0000100000000000b
TX_NAK				EQU	0001000000000000b
RX_SEQ_ERR			EQU	0010000000000000b
RX_BAD_CHECKSUM	EQU	0100000000000000b
RX_COM_ERR			EQU	1000000000000000b

TXMAXRESET			EQU	3
TXMAXERROR			EQU	3
TXMAXIDLE			EQU	4

						;		080h...0FBh		;command codes
MAX_COMMAND_CHAR	EQU	0FBh				;largest command code allowed
ACK_CHAR				EQU	0FCh
NAK_CHAR				EQU	0FDh
RESTART_CHAR		EQU	0FEh
IDLE_CHAR			EQU	0FFh

RX_ACK_BIT			EQU	00000001b
RX_NAK_BIT			EQU	00000010b

PCSPAK STRUC
			portBase			dw ?				;base port address
			ErrorValue		dw ?

			enabled			db ?				;set if rx/tx enabled
			txHoldOff		db ?
			txReply			db ?
			txState			db ?
			txCount			db ?
			txResetCount	db ?
			txErrorCount	db ?
			txIdleCount		db ?
			txChecksum		db ?
			txMaximum		db ?

			rxFlags			db ?
			rxState			db ?
			rxCount			db ?
			rxChecksum		db ?
			rxMaximum		db ?

			txTablePtr		dd ?
			rxTablePtr		dd ?
										 
			intDataPtr		dd ?				;pointer to IRQDATA structure

			txBuf				COMBUF  <>
			rxBuf				COMBUF  <>
PCSPAK ENDS

HISTORYSIZE			equ	256

IRQ_EVENT			equ	0
IRQ_EVENT_RX		equ  	1
IRQ_EVENT_TX		equ  	2
IRQ_EVENT_MSR		equ  	3
IRQ_EVENT_LSR		equ  	4
IRQ_EVENT_BOGUS	equ  	5

TX_EVENT_RESTART	equ	10
TX_EVENT_ABANDON	equ	11
TX_EVENT_DONE		equ	12
TX_EVENT_ACK		equ	13
TX_EVENT_NAK		equ	14
TX_EVENT_EARLY		equ	15
TX_EVENT_EMPTY		equ	16
TX_EVENT_KICK		equ	17

RX_EVENT_FULL		equ	20
RX_EVENT_NOT_CMD	equ	21
RX_EVENT_FULLBODY	equ	22
RX_EVENT_NAK		equ	23
RX_EVENT_CKSMERR	equ	24
RX_EVENT_OK			equ	25

if KEEP_HISTORY

	SAVE_TX MACRO
		LOCAL skip

		push	ax
		push	ebx

		mov	ebx,historyIndex
		cmp	ebx,HISTORYSIZE
		jge	skip

		mov	ah,01h
		mov	history[ebx],ax
		add	ebx,2
		mov	historyIndex,ebx

		inc	pcspakCharactersSent		;;keep a running count
skip:   
		pop	ebx
		pop	ax
	ENDM

	SAVE_RX MACRO
		LOCAL skip

		push	ax
		push	ebx

		mov	ebx,historyIndex
		cmp	ebx,HISTORYSIZE
		jge	skip

		mov	ah,02h
		mov	history[ebx],ax
		add	ebx,2
		mov	historyIndex,ebx

skip:   
		pop	ebx
		pop	ax
	ENDM

	SAVE_EVENT MACRO marker
		LOCAL skip

		push	ax
		push	ebx

		mov	ebx,historyIndex
		cmp	ebx,HISTORYSIZE
		jge	skip

		mov	ah,03h
		mov	al,marker
		mov	history[ebx],ax
		add	ebx,2
		mov	historyIndex,ebx

skip:   
		pop	ebx
		pop	ax
	ENDM
else
	SAVE_TX MACRO
		inc	pcspakCharactersSent		;;keep a running count
	ENDM

	SAVE_RX MACRO
	ENDM

	SAVE_EVENT MACRO marker
	ENDM
endif

;===========================================================================
; Data segment
;===========================================================================
BEGIN_DATA

data_selector	dw ?
irq_com1			IRQDATA	<IntCom1, 0,0,0,0,0,0,0,0>
irq_com2			IRQDATA	<IntCom2, 0,0,0,0,0,0,0,0>
irq_com3			IRQDATA	<IntCom3, 0,0,0,0,0,0,0,0>
irq_com4			IRQDATA	<IntCom4, 0,0,0,0,0,0,0,0>

	public historyIndex
historyIndex				dd	0

	public history
if KEEP_HISTORY
	history					dw	HISTORYSIZE	dup(0)
else
	history					dw	0
endif

	public iThinkIRQIsOn
iThinkIRQIsOn				dd	0

	public pcspakCharactersSent
pcspakCharactersSent		dd	0

	public previousIMR,previousISR,previousIRR
previousIMR		db	0
previousISR		db	0
previousIRR		db	0

	public middleIMR,middleISR,middleIRR
middleIMR		db	0
middleISR		db	0
middleIRR		db	0

	public postIMR,postISR,postIRR
postIMR			db	0
postISR			db	0
postIRR			db	0

	public pcspakLSR,pcspakIER,pcspakIIR
pcspakLSR		db	0
pcspakIER		db	0
pcspakIIR		db	0



global_serial_port		dw	0

END_DATA
;===========================================================================
; Code segment
;===========================================================================
BEGIN_CODE

;-------------------------------------------------------------------------
; PCSPAKInit()
; Constructor for serial packet driver interface
;-------------------------------------------------------------------------
; Enter:
;		init_this		= pointer to object
;		init_port		= address of serial port
;		init_irq_num	= interrupt number
;		init_rx_len 	= pointer to (byte) receive length table
;		init_tx_len 	= pointer to (byte) transmit length table
;-------------------------------------------------------------------------
; Returns:
;		 1 if ok,
;		-1 if IRQ's all used,
;		-2 if DPMI error,
;		-3 if really weird bogus error,
;		else IIR value
;-------------------------------------------------------------------------
BEGIN_C_PROC PCSPAKInit <ebx,ecx,edx,esi,edi,es>

			ARG_DPTR	init_this
			ARG_WORD init_rate
			ARG_WORD	init_port
			ARG_WORD	init_irq_num
			ARG_DPTR	init_rx_len
			ARG_WORD init_rx_count
			ARG_DPTR	init_tx_len
			ARG_WORD init_tx_count

			LOCAL_DWORD	return_status
										 
			BUILD_STACK_FRAME

			mov	return_status,0			;everything's OK
			;--------------------------------------------------------------
			; Save off the data selector so we can get it back later.
			;--------------------------------------------------------------
			; ...In flat mode, CS and DS overlap, but CS is write-protected
			; and DS isn't.  Therefore we can write DS to [data_selector]
			; and read it back from CS later.
			;--------------------------------------------------------------
			mov	[data_selector],ds
			;--------------------------------------------------------------
			; Disable interrupts
			;--------------------------------------------------------------
			DPMI_DISABLE_SAVE_INT			;pushes ax!
			IRQ_WATCH 0
			;--------------------------------------------------------------
			; Point ESI at object data
			;--------------------------------------------------------------
			mov	esi,init_this
			;--------------------------------------------------------------
			; Clear the data buffers
			;--------------------------------------------------------------
			COM_INIT rxBuf						;assumes esi points to object
			COM_INIT txBuf						;assumes esi points to object
			;--------------------------------------------------------------
			; Initialize values
			;--------------------------------------------------------------
			xor	eax,eax
			mov	enabled[esi],al			;clear 'enabled' flag

			mov	txHoldOff[esi],al			;flag as not held off
			mov	txReply[esi],al			;clear 'reply' char
			mov	rxFlags[esi],al			;clear receive flags (ack/nak)
			mov	ErrorValue[esi],ax		;clear errors
			mov	intDataPtr[esi],eax		;(flag as not allocated)

			mov	historyIndex,eax

			mov	al,TX_IDLE_STATE			;initialize to idle state
			mov	txState[esi],al

			mov	al,RX_IDLE_STATE			;initialize to idle state
			mov	rxState[esi],al

			mov	ax,init_port				;get the port address
			mov	portBase[esi],ax			;save it

			mov	eax,init_tx_len			;set dialect transmit table pointer
			mov	txTablePtr[esi],eax

			mov	ax,init_tx_count			;set maximum dialect transmit index
			mov	txMaximum[esi],al

			mov	eax,init_rx_len			;set dialect receive table pointer
			mov	rxTablePtr[esi],eax

			mov	ax,init_rx_count			;set maximum dialect receive index
			mov	rxMaximum[esi],al
			;--------------------------------------------------------------
			; Find unused IRQDATA structure
			;--------------------------------------------------------------
			mov	ecx,4
			mov	edi,OFFSET irq_com1
searchLoop:
			mov	ax,irqPort[edi]			;is irqPort equal to zero?
			je		short searchDone			;yes, use this structure
			add	edi,SIZE IRQDATA			;no, move to next one
			loop   searchLoop

			mov	ErrorValue[esi],ERR_INIT ;allocation failed, set errors!
			mov	return_status,-1			;return an error code
			jmp	allocFailed

searchDone:
			mov	ax,init_port				;save port address
			mov	irqPort[edi],ax
			mov	global_serial_port,ax	;save in case of crash

			mov	ax,init_irq_num			;save interrupt number
			mov	irqNum[edi],al
			;--------------------------------------------------------------
			; Save init_this in irq structure, irqPtr in "this" structure
			;--------------------------------------------------------------
			mov	irqThis[edi],esi
			mov	intDataPtr[esi],edi
			;--------------------------------------------------------------
			; Save the old PROTECTED MODE interrupt vector
			;--------------------------------------------------------------
			mov	bl,irqNum[edi]				;bl has interrupt number
			DPMI_GET_PROT_VECTOR				;returns cx:edx
			mov	irqOldSelector[edi],cx
			mov	irqOldOffset[edi],edx
			;--------------------------------------------------------------
			; Set the new PROTECTED MODE interrupt vector
			;--------------------------------------------------------------
			mov	bx,init_irq_num			;bl has interrupt number
			mov	cx,cs							;cx:edx has selector:offset
			mov	edx,irqHandler[edi]
			DPMI_SET_PROT_VECTOR
			jnc	short allocOk				;carry clear, allocation succeeded

			mov	ErrorValue[esi],ERR_INIT ;allocation failed, set errors!
			mov	return_status,-2			;return an error code
			jmp	allocFailed
allocOk:
			;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
			; Diagnostics values
			;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
			PIC_IMR_TO_AL
			mov	previousIMR,al
			PIC_ISR_TO_AL
			mov	previousISR,al
			PIC_IRR_TO_AL
			mov	previousIRR,al
			;--------------------------------------------------------------
			; Initialize PIC
			;--------------------------------------------------------------
			;-----------------------------
			; Get master PIC data:
			;   enable bit in bl,
			;   interrupt number in bh
			;-----------------------------
			mov	ax,init_irq_num			;get interrupt number
			cmp	ax,0000Ch					;COM1?
			jnz	short mustBeCom2			;no, must be COM2

			mov	bl,PIC_COM1COM3			;yes, get COM1, COM3 enable...
			mov	bh,OCW2_SPEC_EOI+4		;...and PIC specific EOI
			jmp	short hasPicBits

mustBeCom2:
			mov	bl,PIC_COM2COM4			;get COM2, COM4 enable...
			mov	bh,OCW2_SPEC_EOI+3		;...and PIC specific EOI
hasPicBits:        
			mov	irqPICbit[edi],bl			;save for termination
			;-----------------------------
			; Enable interrupts in PIC
			;-----------------------------
			ASSERT_PIC_REG bl
			;-----------------------------
			; Make sure PIC interrupt
			; request is cleared
			;-----------------------------
			mov	al,bh							;output the specific EOI
			out	PIC_MASTER_0,al
			;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
			; Diagnostics values
			;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
			PIC_IMR_TO_AL
			mov	middleIMR,al
			PIC_ISR_TO_AL
			mov	middleISR,al
			PIC_IRR_TO_AL
			mov	middleIRR,al
			;--------------------------------------------------------------
			; Initialize UART
			;--------------------------------------------------------------
			mov	dx,init_port				;get port address from stack
			;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
			; Diagnostics values
			;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
			add	dx,UART_IIR
			in		al,dx
			mov	pcspakIIR,al
			sub	dx,UART_IIR
			;-----------------------------
			; Disable UART interrupts
			;-----------------------------
			RETRACT_MCR	 UART_MCR_IRQ		;This clears irq into PIC
			;-----------------------------
			; Disable all UART interrupt
			; sources.  Not mandatory,
			; but a safe practice.
			;-----------------------------
			RETRACT_IER	 UART_IER_ALL
			;-----------------------------
			; Set baud rate
			; Side effect: the divisor
			; latch access bit is cleared
			;-----------------------------
			mov	cx,init_rate				;set data rate
			SETBAUD_CX							;macro from PCSERIAL.INC
			;-----------------------------
			; Set data format:
			;   8 data bits,
			;   1 stop bit,
			;   no parity
			; Side effect: the divisor
			; latch access bit is cleared
			;-----------------------------
			mov	al,UART_LCR_8+UART_LCR_S1+UART_LCR_NP
			SETFORMAT_AL


			;-----------------------------
			; Enable UART interrupt sources
			;-----------------------------
			ASSERT_IER	 UART_IER_ALL
			;-----------------------------
			; Enable UART interrupts, RTS
			;-----------------------------
			ASSERT_MCR	 UART_MCR_IRQ+UART_MCR_RTS
			;-----------------------------
			; Clear UART interrupt sources
			;-----------------------------
			in		al,dx							;clear receiver interrupt (read inputs)
			in		al,dx

;;			mov	al,IDLE_CHAR				; clear transmit interrupt
;;			out	dx,al
;;			out	dx,al

			add	dx,UART_MSR					;read modem status reg to clear IRQ
			in		al,dx							;discard the result
			sub	dx,UART_MSR
			
			add	dx,UART_LSR					;read line status reg to clear IRQ
			in		al,dx							;read value
			mov	pcspakLSR,al				;(save for diagnostics)
			sub	dx,UART_LSR
			;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
			; Diagnostics values
			;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
			add	dx,UART_IER
			in		al,dx
			mov	pcspakIER,al
			sub	dx,UART_IER

			PIC_IMR_TO_AL
			mov	postIMR,al
			PIC_ISR_TO_AL
			mov	postISR,al
			PIC_IRR_TO_AL
			mov	postIRR,al
			;--------------------------------------------------------------
			; Mark as 'enabled'
			;--------------------------------------------------------------
			mov	enabled[esi],1

allocFailed:
			;--------------------------------------------------------------
			; Reenable the old interrupt state and return
			;--------------------------------------------------------------
			DPMI_RESTORE_PREV_INT			;pops ax!
			IRQ_WATCH 1

			mov	eax,return_status

END_C_PROC

;-------------------------------------------------------------------------
; PCSPAKTerm()
; Destructor for serial packet interface
;-------------------------------------------------------------------------
; Enter:
;		  (this)  = pointer to object
;-------------------------------------------------------------------------
; Returns:
;		  (nothing)
;-------------------------------------------------------------------------
BEGIN_C_PROC PCSPAKTerm <eax,ebx,ecx,edx,esi,edi>

			ARG_DPTR	term_this
			BUILD_STACK_FRAME

			;--------------------------------------------------------------
			; Disable interrupts
			;--------------------------------------------------------------
			DPMI_DISABLE_SAVE_INT			;pushes ax!
			IRQ_WATCH 0
			;--------------------------------------------------------------
			; Get "this" pointer
			;--------------------------------------------------------------
			mov	esi,term_this
			mov	eax,esi						;is it NULL?
			or		eax,eax
			jz		short skipRestore			;OHMIGOSH!! EXIT!!!
			;--------------------------------------------------------------
			; Turn off RTS and interrupts
			;--------------------------------------------------------------
			mov	dx,portBase[esi]
			RETRACT_MCR	 UART_MCR_IRQ+UART_MCR_RTS

			; NOTE: from this point on, interrupts CANNOT occur from the UART.

			;--------------------------------------------------------------
			; If improperly allocated, skip 'release'
			;--------------------------------------------------------------
			mov	eax,intDataPtr[esi]		;get pointer to IRQ data
			or		eax,eax
			jz		short skipRestore			;null pointer, skip release

			mov	edi,eax						;place pointer in edi
			;--------------------------------------------------------------
			; Mark IRQDATA record as unused
			;--------------------------------------------------------------
			mov	irqPort[edi],0
			;--------------------------------------------------------------
			; Restore the old PROTECTED MODE interrupt vector
			;--------------------------------------------------------------
			mov	bl,irqNum[edi]				;bl has interrupt number
			mov	cx,irqOldSelector[edi]	;cx:edx has selector:offset
			mov	edx,irqOldOffset[edi]
			DPMI_SET_PROT_VECTOR
			;--------------------------------------------------------------
			; Disable PIC channel
			;--------------------------------------------------------------
			mov	bl,irqPICbit[edi]			;get our PIC bit
			RETRACT_PIC bl						;no, turn it off
skipRestore:
			;--------------------------------------------------------------
			; Reenable the old interrupt state and return
			;--------------------------------------------------------------
			DPMI_RESTORE_PREV_INT			;pops ax!
			IRQ_WATCH 1

END_C_PROC

;-------------------------------------------------------------------------
; PCSPAKReceive()
; Returns packet if available
;-------------------------------------------------------------------------
; Enter:
;		rp_this			= pointer to object
;		Byte *destPtr	= pointer to 128-byte destination buffer
;-------------------------------------------------------------------------
; Returns:
;		  int packetSize (or zero if no packet available)
;-------------------------------------------------------------------------
BEGIN_C_PROC PCSPAKReceive <ebx,ecx,edx,esi,edi>

			ARG_DPTR	rp_this
			ARG_DPTR rp_dest
			BUILD_STACK_FRAME

			;--------------------------------------------------------------
			; Get "this" pointer
			;--------------------------------------------------------------
			mov	esi,rp_this
			mov	eax,esi						;is it NULL?
			or		eax,eax
			jz		spr_done						;OHMIGOSH!! EXIT!!!
			;--------------------------------------------------------------
			; Skip if disabled
			;--------------------------------------------------------------
			mov	al,enabled[esi]			;if disabled, don't even try
			or		al,al
			jnz	short recvOK

			xor	eax,eax
			jmp	spr_done
recvOK:
			;--------------------------------------------------------------
			; Check character count: non-zero indicates entire packet
			;--------------------------------------------------------------
			xor	eax,eax						;clear top of eax
			mov	ax,rxBuf.count[esi]		;get # of characters in ax
			or		ax,ax							;empty?
			jz		spr_done						;yes, return zero
;;;			;--------------------------------------------------------------
;;;			; Disable interrupts
;;;			;--------------------------------------------------------------
;;;			DPMI_DISABLE_INT
;;;			IRQ_WATCH 0
			;--------------------------------------------------------------
			; Get buffer length
			;--------------------------------------------------------------
			mov	edi,rp_dest					;get pointer to destination
			cld									;make sure direction is ok

			xor	eax,eax						;clear top of ax
			COM_GET_AL rxBuf					;get character into al
			;--------------------------------------------------------------
			; Verify first char is a valid command character
			;--------------------------------------------------------------
			test	al,080h						;1st character = command (high bit set)?
			jnz	short sprOk

			DISABLE_AND_DIE 9
			xor	eax,eax
			jmp	short spr_done
sprOk:
			stosb									;save it
			and	al,07Fh						;remove MSB
			mov	ebx,rxTablePtr[esi]		;point bx at length table
			xlatb									;translate into length
			xor	ecx,ecx						;set ecx from al
			mov	cl,al
			mov	edx,ecx						;save in edx
			inc	edx							;return at least one character
			;--------------------------------------------------------------
			; Transfer data from buffer
			;--------------------------------------------------------------
			or		cx,cx							;if no additional characters, skip
			jz		short spr_skip
spr_loop:
			COM_GET_AL rxBuf					;get character into al
			stosb									;save in buffer
			loop	spr_loop
spr_skip:
			COM_ACCEPT_OUT rxBuf				;accept all 'taken' characters
;;;			;--------------------------------------------------------------
;;;			; Reenable the old interrupt state
;;;			;--------------------------------------------------------------
;;;			DPMI_RESTORE_PREV_INT			;pops ax!
;;;			IRQ_WATCH 1

			mov	eax,edx						;place count in eax
			;--------------------------------------------------------------
			; Return count in eax
			;--------------------------------------------------------------
spr_done:
END_C_PROC

;-------------------------------------------------------------------------
; PCSPAKSend()
; Transmits packet
;-------------------------------------------------------------------------
; Enter:
;		sp_this	  = pointer to object
;		Byte *sp_src = pointer to (up to) 128-byte source buffer
;-------------------------------------------------------------------------
; Returns:
;		void
;-------------------------------------------------------------------------
BEGIN_C_PROC PCSPAKSend	<eax,ebx,ecx,edx,esi,edi>

			ARG_DPTR	sp_this
			ARG_DPTR	sp_src

			BUILD_STACK_FRAME
			;--------------------------------------------------------------
			; Get "this" pointer
			;--------------------------------------------------------------
			mov	esi,sp_this
			mov	eax,esi						;is it NULL?
			or		eax,eax
			jz		sendSkip						;OHMIGOSH!! EXIT!!!
			;--------------------------------------------------------------
			; Wait for sufficient room
			;--------------------------------------------------------------
			mov	al,enabled[esi]			;if disabled, don't even try
			or		al,al
			jz		sendSkip

;;;			SET_LPT 5

			mov	edi,sp_src					;get pointer to source
			mov	al,[edi]						;get the command byte
			and	al,07Fh						;remove MSB
			mov	ebx,txTablePtr[esi]		;set ebx to point to length table
			xlatb									;get the length in al
			xor	ecx,ecx						;move al to ecx
			mov	cl,al
			inc	cx								;add one for command byte
spt_wait:
			COM_SPACE_IN txBuf,ax			;get buffer 'in' space
			cmp	ax,cx							;enough room?
			jae	spt_space					;yes, continue

			DISABLE_AND_DIE 0
			jmp	short spt_wait

spt_space:
			;--------------------------------------------------------------
			; Disable interrupts
			;--------------------------------------------------------------
			DPMI_DISABLE_SAVE_INT			;pushes ax!
			IRQ_WATCH 0
			;--------------------------------------------------------------
			; Copy data into tx buffer
			;--------------------------------------------------------------
spt_loop:
			mov	al,[edi]						;get the byte
			inc	edi							;increment the source pointer
			COM_PUT_AL txBuf					;place it in the buffer
			loop    spt_loop

			COM_ACCEPT_IN txBuf				;accept all 'placed' characters
			mov	dx,portBase[esi]			;set dx to port base address
			call	StartSending				;attempt to start tx interrupt
			;--------------------------------------------------------------
			; Reenable the old interrupt state
			;--------------------------------------------------------------
			DPMI_RESTORE_PREV_INT			;pops ax!
			IRQ_WATCH 1

;;;			CLEAR_LPT 5

sendSkip:
END_C_PROC

;-------------------------------------------------------------------------
; PCSPAKActive()
; Returns transmitter 'active' state (=0 when all transmission is done).
;-------------------------------------------------------------------------
; Enter:
;		  pa_this  = pointer to object
;-------------------------------------------------------------------------
; Returns:
;		  int    errors
;-------------------------------------------------------------------------
BEGIN_C_PROC PCSPAKActive	<ebx,ecx,edx,esi>

			ARG_DPTR	pa_this
			BUILD_STACK_FRAME
			;--------------------------------------------------------------
			; Get "this" pointer
			;--------------------------------------------------------------
			mov	esi,pa_this
			mov	eax,esi						;is it NULL?
			or		eax,eax
			jz		short actNULL				;OHMIGOSH!! EXIT!!!
			;--------------------------------------------------------------
			; Get 'active' status
			;
			; The LSR register has two bits reflecting the
			; transmitter state:
         ;     THR = transmit holding register (1=empty)
			;     TSR = transmit serial register (1=empty)
			;
			; The transmission is completely finished when both of these
			; bits are high.
			;--------------------------------------------------------------
			xor	eax,eax						;clear top of eax

			mov	dx,portBase[esi]			;get the port base
			add	dx,UART_LSR					;get LSR
			in		al,dx
			;;sub	dx,UART_LSR					;we usually subtract, no need here

			and	al,UART_LSR_THR+UART_LSR_TSR	; remove all but THR,TSR
			xor	al,UART_LSR_THR+UART_LSR_TSR	; invert the bits
actNULL:
END_C_PROC

;-------------------------------------------------------------------------
; PCSPAKErrors()
; Returns error bits: zero indicates no errors.
; The error code is cleared by this call as well.
;-------------------------------------------------------------------------
; Enter:
;		  pe_this  = pointer to object
;-------------------------------------------------------------------------
; Returns:
;		  int    errors
;-------------------------------------------------------------------------
BEGIN_C_PROC PCSPAKErrors	<ebx,ecx,edx,esi>

			ARG_DPTR	pe_this
			BUILD_STACK_FRAME
			;--------------------------------------------------------------
			; Get "this" pointer
			;--------------------------------------------------------------
			mov	esi,pe_this
			mov	eax,esi						;is it NULL?
			or		eax,eax
			jz		short errNULL						;OHMIGOSH!! EXIT!!!
			;--------------------------------------------------------------
			; Get, clear error word
			;--------------------------------------------------------------
			mov	ax,ErrorValue[esi]		;init error?
			and	ax,ERR_INIT
			jnz	short haveInitError				;yes, don't clear it

			xor	ax,ax							;no, go ahead and clear it
			xchg	ax,ErrorValue[esi]
haveInitError:
			and	eax,0FFFFh					;make sure top of eax is clear
errNULL:
END_C_PROC

;-------------------------------------------------------------------------
; PCSPAKIRQState()
; Returns IRQ state
;-------------------------------------------------------------------------
; Enter:
;       (void)
;-------------------------------------------------------------------------
; Returns:
;		  int    state
;-------------------------------------------------------------------------
BEGIN_C_PROC PCSPAKIRQState	<ebx,ecx,edx,esi>

			BUILD_STACK_FRAME

			DPMI_INT_STATE
			and	eax,0FFh					;make sure top of eax is clear
END_C_PROC

;-------------------------------------------------------------------------
; PCSPAKTestInstance()
;-------------------------------------------------------------------------
; Enter:
;		  (this)  = pointer to object
;-------------------------------------------------------------------------
; Returns:
;		  int	True
;-------------------------------------------------------------------------
BEGIN_C_PROC PCSPAKTestInstance
			mov	eax,1
			ret 
END_C_PROC

;-------------------------------------------------------------------------
; PCSPAKIsSignatureBad()
;-------------------------------------------------------------------------
; Enter:
;		  (this)  = pointer to object
;-------------------------------------------------------------------------
; Returns:
;		  int	False
;-------------------------------------------------------------------------
BEGIN_C_PROC PCSPAKIsSignatureBad
			mov	eax,0
			ret 
END_C_PROC

;-------------------------------------------------------------------------
; PCSPAKReceiveCount()
;-------------------------------------------------------------------------
; Enter:
;		  (this)  = pointer to object
;-------------------------------------------------------------------------
; Returns:
;		  int		number of items in receive queue
;-------------------------------------------------------------------------
BEGIN_C_PROC PCSPAKReceiveCount	<esi>

			ARG_DPTR	prc_this
			BUILD_STACK_FRAME

			mov	esi,prc_this
			xor	eax,eax
			mov	ax,rxBuf.count[esi]
END_C_PROC

;-------------------------------------------------------------------------
; PCSPAKTransmitCount()
;-------------------------------------------------------------------------
; Enter:
;		  (this)  = pointer to object
;-------------------------------------------------------------------------
; Returns:
;		  int		number of items in transmit queue
;-------------------------------------------------------------------------
BEGIN_C_PROC PCSPAKTransmitCount	<esi>

			ARG_DPTR	ptc_this
			BUILD_STACK_FRAME

			mov	esi,ptc_this
			xor	eax,eax
			mov	ax,txBuf.count[esi]
END_C_PROC


;-------------------------------------------------------------------------
; PCSPAKSetDTR()
;-------------------------------------------------------------------------
; Enter:
;		  (this)  = pointer to object
;        on     = zero to retract DTR, non-zero to assert DTR
;-------------------------------------------------------------------------
; Returns:
;		  int		number of items in transmit queue
;-------------------------------------------------------------------------
BEGIN_C_PROC PCSPAKSetDTR	<esi>

			ARG_DPTR		sdtr_this
			ARG_DWORD	sdtr_on
			BUILD_STACK_FRAME
			;--------------------------------------------------------------
			; Get "this" pointer
			;--------------------------------------------------------------
			mov	esi,sdtr_this
			mov	eax,esi						;is it NULL?
			or		eax,eax
			jz		short sdtrExit				;OHMIGOSH!! EXIT!!!

			mov	dx,portBase[esi]			;get the port base
			;--------------------------------------------------------------
			; Get "on"
			;--------------------------------------------------------------
			cmp	sdtr_on,0
			je		sdtrOff	
			;--------------------------------------------------------------
			; Assert DTR
			;--------------------------------------------------------------
			ASSERT_MCR	 UART_MCR_DTR
			jmp	short sdtrExit
			;--------------------------------------------------------------
			; Retract DTR
			;--------------------------------------------------------------
sdtrOff:
			RETRACT_MCR	 UART_MCR_DTR
sdtrExit:

END_C_PROC

;-------------------------------------------------------------------------
; StartSending - attempt to start transmitter (if allowed)
; ...called from the receive interrupt routine and PCSPAK::sendPacket.
;-------------------------------------------------------------------------
; Enter:
;		  ESI = pointer to PCSPAK structure
;		  DX  = UART base address
;-------------------------------------------------------------------------
; Returns:
;		  (void)
;-------------------------------------------------------------------------
; Side effects:
;		  Modifies register AL
;-------------------------------------------------------------------------
StartSending    PROC NOLANGUAGE NEAR

			add	dx,UART_LSR					;get THR bit from LSR
			in		al,dx
			sub	dx,UART_LSR

			test	al,UART_LSR_THR			;is there anything in the holding reg?
			jz		sptNoKick					;yes, interrupt will occur later

			SAVE_EVENT TX_EVENT_KICK
			call	SendChar						;restart transmitter
sptNoKick:
			ret
StartSending    ENDP

;-------------------------------------------------------------------------
; SendChar - send character through UART
; ...called from the interrupt routine and PCSPAK::sendCommand.
;-------------------------------------------------------------------------
; Enter:
;		  ESI = pointer to PCSPAK structure
;		  DX  = UART base address
;-------------------------------------------------------------------------
; Returns:
;		  (void)
;-------------------------------------------------------------------------
; Side effects:
;		  registers EAX, EBX, ECX modified
;-------------------------------------------------------------------------
SendChar	PROC NOLANGUAGE NEAR
;;;			SET_LPT 6

			;--------------------------------------------------------------
			; Send reply character if one is waiting
			;--------------------------------------------------------------
			cmp	txReply[esi],0				;"reply" character waiting?
			je		noReply						;no, continue

			xor	al,al							;prepare for exchange
			xchg	al,txReply[esi]			;get and clear the "reply" character
			out	dx,al							;yes, send the reply character
			SAVE_TX
			jmp	sendDone						;done for now.
noReply:
			;--------------------------------------------------------------
			; Enter the appropriate state handler
			;--------------------------------------------------------------
			mov	al,txState[esi]			;get the transmit state

			cmp	al,TX_IDLE_STATE			;idle state?
			je		short txIdleState			;yes, go to it

			cmp	al,TX_BODY_STATE			;"body" state?
			je		txBodyState					;yes, go to it

			cmp	al,TX_WAIT_STATE			;"wait" state?
			je		txWaitState					;yes, go to it

			cmp	al,TX_RESTART_STATE		;restart state?
			je		short txRestartState		;yes, go to it

			DISABLE_AND_DIE 1
			jmp	txWaitState					;must be "wait" state
			;--------------------------------------------------------------
			; Transmitter idle state
			;--------------------------------------------------------------
txIdleState:
			cmp	txHoldOff[esi],0			;If transmitter 'held off', do nothing
			jne	short txIsIdle

			cmp	txBuf.count[esi],0		;input buffer available?
			jne	short txIdleAvail			;yes, send something
txIsIdle:
			SAVE_EVENT TX_EVENT_EMPTY		;nothing to send, just return
			jmp	sendDone

txIdleAvail:
			;---------------------------
			; Buffer ready, start
			;---------------------------
			mov	txResetCount[esi],TXMAXRESET	;set the 'reset' counter
			mov	txErrorCount[esi],TXMAXERROR	;set the 'error' counter

			;...deliberately fall through into 'txRestartState'

			;--------------------------------------------------------------
			; Transmitter restart state
			;--------------------------------------------------------------
txRestartState:
			COM_GET_AL txBuf					;assumes esi points to object

			cmp	al,07Fh						;is it a command?
			jbe	short txRestartBad		;no, discard it

			mov	ah,al							;transfer to ah
			and	ah,07Fh						;remove MSB
			cmp	ah,txMaximum[esi]			;legitimate command?
			jb		short txRestartOk			;yes

txRestartBad:
			DISABLE_AND_DIE 2
			COM_ACCEPT_OUT  txBuf		   ;eat the character
			mov	txState[esi],TX_IDLE_STATE ;move to the "idle" state
			jmp	sendDone

txRestartOk:
			out	dx,al							;transmit the command character
			mov	txChecksum[esi],al		;begin a new checksum count
			SAVE_TX

			mov	al,ah							;move AND'ed value back to al
			mov	ebx,txTablePtr[esi]		;get length table pointer into ebx
			xlatb									;look up length
			inc	al								;include the command byte
			mov	txCount[esi],al			;save the length

			mov	txState[esi],TX_BODY_STATE ;move to the "body" state
			jmp	sendDone

			;--------------------------------------------------------------
			; Transmitter "body" state
			; Send message body, then checksum (unless interrupted by ACK/NAK)
			;--------------------------------------------------------------
txBodyState:
			;---------------------------
			; Interrupted by ACK/NAK?
			;---------------------------
			cmp	rxFlags[esi],0				;flag set?
			je		txBodySend					;no, send normal body

			COM_RESTORE_OUT txBuf			;interrupted, reset 'out' pointer

			mov	al,rxFlags[esi]			;get flags
			mov	rxFlags[esi],0				;clear them

			test	al,RX_ACK_BIT				;was it an ACK?
			jne	short txBodyNak			;no
			or		ErrorValue[esi],TX_EARLY_ERR ;yes, set error flag
txBodyNak:
			SAVE_EVENT TX_EVENT_EARLY
			;---------------------------
			; Restart or die
			;---------------------------
			dec	txResetCount[esi]			;check the restart count
			jl		short txBodyGiveUp		;if too many errors, give up
			;---------------------------
			; Attempt restart
			;---------------------------
			mov	al,RESTART_CHAR			;no, send restart character
			out	dx,al
			SAVE_TX

			mov	txState[esi],TX_RESTART_STATE ;move to the "restart" state
			jmp	sendDone
			;---------------------------
			; Too many errors, die!
			;---------------------------
txBodyGiveUp:
			SAVE_EVENT TX_EVENT_ABANDON
			or		ErrorValue[esi],TX_ABANDON ;set error flag
			mov	ebx,txTablePtr[esi]		;get pointer to length table
			COM_EAT_PACKET  txBuf			;discard current packet
			jmp	txIdleState					;start another packet
			;---------------------------
			; Send body or checksum
			;---------------------------
txBodySend:
			dec	txCount[esi]				;is buffer done?
			jle	txBodyCheck					;yes, send checksum
			;---------------------------
			; Send a body character
			;---------------------------
			COM_GET_AL txBuf					;get a character from the buffer

			cmp	al,07Fh						;legitimate body char?
			jbe	txBodyOk						;yes, continue
			DISABLE_AND_DIE 3
			and	al,07Fh						;prevent bad data from becoming command
txBodyOk:
			out	dx,al							;send it
			add	txChecksum[esi],al		;add to the checksum
			SAVE_TX
			jmp	sendDone
			;---------------------------
			; End of body, send checksum
			;---------------------------
txBodyCheck:
			mov	al,txChecksum[esi]		;get the checksum
			and	al,07Fh						;limit it
			out	dx,al							;send it
			SAVE_TX
			SAVE_EVENT TX_EVENT_DONE
			;---------------------------
			; prepare for "wait" state
			;---------------------------
			mov	txIdleCount[esi],TXMAXIDLE ;set the idle counter
			mov	txState[esi],TX_WAIT_STATE ;move to the "wait" state
			jmp	sendDone

			;--------------------------------------------------------------
			; Transmitter "wait" state
			; Waits for reply from other end
			;--------------------------------------------------------------
txWaitState:
			xor	al,al							;prepare to exchange flags
			xchg	al,rxFlags[esi]			;get flags, also clear them
			or		al,al							;flags clear?
			jz		txWaitMore					;yes, keep waiting

			test	al,RX_ACK_BIT				;ACK?
			jz		short txWaitNak			;no, process NAK
			;---------------------------
			; ACK, buffer received OK
			;---------------------------
			SAVE_EVENT TX_EVENT_ACK
			COM_ACCEPT_OUT  txBuf		   ;yes, good transmission. Discard packet.
			mov	txState[esi],TX_IDLE_STATE ;move to the "idle" state
			jmp	txIdleState					;start another buffer
			;---------------------------
			; NAK!
			;---------------------------
txWaitNak:
			SAVE_EVENT TX_EVENT_ACK
			or		ErrorValue[esi],TX_NAK	;set error flag
			COM_RESTORE_OUT txBuf			;restore output pointers

			dec	txErrorCount[esi]			;check the error count
			jl		txWaitGiveUp				;if too many errors, give up
			jmp	txRestartState				;no, resend it
			;---------------------------
			; Keep waiting
			;---------------------------
txWaitMore:
			dec	txIdleCount[esi]			;have we waited too long?
			jl		short txWaitDone			;yes, attempt to restart

			mov	al,IDLE_CHAR				;no, send idle character
			out	dx,al
			SAVE_TX
			jmp	sendDone
			;---------------------------
			; Attempt restart
			;---------------------------
txWaitDone:
			SAVE_EVENT TX_EVENT_RESTART
			or		ErrorValue[esi],TX_RESTART ;set error flag
			COM_RESTORE_OUT txBuf			;restore output pointers

			dec	txResetCount[esi]			;too many restarts?
			jl		short txWaitGiveUp		;yes, abandon buffer

			mov	al,RESTART_CHAR			;no, send restart character
			out	dx,al
			SAVE_TX

			mov	txIdleCount[esi],TXMAXIDLE ;reset the idle counter
			mov	txState[esi],TX_RESTART_STATE ;move to the "idle" state
			jmp	short sendDone
			;---------------------------
			; Give up
			;---------------------------
txWaitGiveUp:
			SAVE_EVENT TX_EVENT_ABANDON
			or		ErrorValue[esi],TX_ABANDON ;set error flag
			mov	ebx,txTablePtr[esi]			;get pointer to length table
			COM_EAT_PACKET  txBuf				;discard the packet
			mov	txState[esi],TX_IDLE_STATE ;move to the "idle" state
			jmp	txIdleState						;start another packet

sendDone:
;;;			CLEAR_LPT 6

			ret

SendChar	ENDP

;-------------------------------------------------------------------------
; RecvChar - receive character from UART
;-------------------------------------------------------------------------
; Enter:
;		ESI = pointer to PCSPAK structure
;		DX  = UART base address
;		AL  = character
;-------------------------------------------------------------------------
; Returns:
;		(void)
;-------------------------------------------------------------------------
; Side effects:
;		registers EAX, EBX, ECX modified
;-------------------------------------------------------------------------
RecvChar PROC NOLANGUAGE NEAR
			SAVE_RX
			;--------------------------------------------------------------
			; Process universal special characters: ACK, NAK, IDLE
			;--------------------------------------------------------------
			test	al,080h						;MSB set?
			jz		short rcNotSpecial		;no, can't be special character
			;------------------------------
			; IDLE
			;------------------------------
			cmp	al,IDLE_CHAR				;IDLE character?
			jne	short rcNotIdle			;no
			jmp	recvDone						;yes, ignore it
rcNotIdle:
			;------------------------------
			; ACK
			;------------------------------
			cmp	al,ACK_CHAR					;ACK character?
			jne	short rcNotAck				;no
			or		rxFlags[esi],RX_ACK_BIT ;yes, flag for transmitter
			jmp	recvDone
rcNotAck:
			;------------------------------
			; NAK
			;------------------------------
			cmp	al,NAK_CHAR					;NAK character?
			jne	short rcNotNak				;no
			or		rxFlags[esi],RX_NAK_BIT ;yes, flag for transmitter
			jmp	recvDone
rcNotNak:
rcNotSpecial:
			;--------------------------------------------------------------
			; Process state-dependent characters
			;--------------------------------------------------------------
			cmp	rxState[esi],0				;test the state
			jne	rxActive						;non-zero, so active
			;--------------------------------------------------------------
			; Receiver "idle" state
			; Wait for command character
			;--------------------------------------------------------------
			test	al,080h						;is it a control character?
			jnz	short rxIdleSpecial		;yes, process it

			or		ErrorValue[esi],RX_SEQ_ERR ;Not expecting data, so error.
			jmp	recvDone

rxIdleSpecial:
			cmp	al,RESTART_CHAR			;restart?
			je		recvDone						;yes, discard it

			COM_SPACE_IN rxBuf,bx			;get remaining space
			or		bx,bx							;zero?
			jnz	short rxIdleSpace			;no, continue

			SAVE_EVENT RX_EVENT_FULL
			DISABLE_AND_DIE 4
			or		ErrorValue[esi],RX_COM_ERR ;error. Set flag.
			jmp	recvDone

rxIdleSpace:
			mov	ah,al							;move to ah for testing
			and	ah,07Fh						;remove MSB
			cmp	ah,rxMaximum[esi]			;legitimate command?
			jb		short rxIdleOk				;yes, continue

			SAVE_EVENT RX_EVENT_NOT_CMD
			DISABLE_AND_DIE 5
			or		ErrorValue[esi],RX_COM_ERR ;no.
			jmp	recvDone
rxIdleOk:
			COM_PUT_AL rxBuf					;save command character in buffer
			mov	rxChecksum[esi],al		;start a new receive checksum

			mov	al,ah							;put ah back into al
			mov	ebx,rxTablePtr[esi]		;get length table pointer into ebx
			xlatb								   ;look up length
			inc	al								;include the command byte
			mov	rxCount[esi],al			;save the length

			mov	rxState[esi],RX_ACTIVE_STATE ;change to "active" state
			jmp	recvDone

			;--------------------------------------------------------------
			; Receiver "active" state
			; Place N characters into buffer, test checksum, and reply
			;--------------------------------------------------------------
rxActive:
			test	al,080h					   ;control character?
			jz		short rxActiveData		;no, therefore data

			or		ErrorValue[esi],RX_SEQ_ERR ;yes, error. Set error flag
			COM_RESTORE_IN rxBuf				;Discard received data
			;------------------------------
			; Process control char
			;------------------------------
			cmp	al,RESTART_CHAR			;restart character?
			je		rxActiveAbandon			;yes, abandon received data

			SAVE_EVENT RX_EVENT_NAK
			mov	al,NAK_CHAR					;error, tell transmitter to send NAK
			jmp	rxActiveReply
			;------------------------------
			; Process data
			;------------------------------
rxActiveData:
			dec	rxCount[esi]				;check the receive count
			jle	rxActiveCheck				;if end of message, process checksum
			;------------------------------
			; Enough space in buffer?
			;------------------------------
			COM_SPACE_IN rxBuf,bx			;get remaining space

			or		bx,bx							;zero?
			jnz	short rxActiveSpace		;no, continue

			SAVE_EVENT RX_EVENT_FULLBODY
			DISABLE_AND_DIE 6					;crash hard (if enabled)
			or		ErrorValue[esi],RX_COM_ERR ;error. Set flag.
			COM_RESTORE_IN rxBuf				;Discard received data
			jmp	rxActiveAbandon			;give up on packet

rxActiveSpace:		
			;------------------------------
			; Save data in buffer
			;------------------------------
			add	rxChecksum[esi],al		;update the checksum
			COM_PUT_AL rxBuf					;save the character
			jmp	recvDone
			;------------------------------
			; Test checksum
			;------------------------------
rxActiveCheck:
			mov	bl,rxChecksum[esi]		;get the checksum
			and	bl,07Fh						;remove MSB
			cmp	al,bl						   ;checksum OK?
			je		short rxActiveOk			;yes!
			;------------------------------
			; Buffer error
			;------------------------------
			SAVE_EVENT RX_EVENT_CKSMERR
			or		ErrorValue[esi],RX_BAD_CHECKSUM ;error. Set flag.
			COM_RESTORE_IN rxBuf				;Discard received data
			mov	al,NAK_CHAR					;tell transmitter to send NAK
			jmp	short rxActiveReply
			;------------------------------
			; Buffer OK
			;------------------------------
rxActiveOk:
			SAVE_EVENT RX_EVENT_OK
			COM_ACCEPT_IN rxBuf				;accept the received data
			mov	al,ACK_CHAR					;tell transmitter to send ACK

rxActiveReply:
			mov	txReply[esi],al
			call	StartSending				;restart transmitter if needed
			;------------------------------
			; Return to inactive state
			;------------------------------
rxActiveAbandon:
			mov	rxState[esi],RX_IDLE_STATE
recvDone:
			ret
RecvChar	ENDP

;-------------------------------------------------------------------------
; Interrupt handlers
;-------------------------------------------------------------------------
IntHandler	 PROC NOLANGUAGE NEAR
			ASSUME cs:_TEXT

IntCom1:
			pushad
			mov	edi,OFFSET irq_com1		;point EDI at IRQDATA structure
			call	IntMain
			popad
			iret
			;jmp	cs:fword ptr [irq_com1.irqOldOffset]
IntCom2:
			pushad
			mov	edi,OFFSET irq_com2		;point EDI at IRQDATA structure
			call	IntMain
			popad
			iret
			;jmp	cs:fword ptr [irq_com2.irqOldOffset]
IntCom3:
			pushad
			mov	edi,OFFSET irq_com3		;point EDI at IRQDATA structure
			call	IntMain
			popad
			iret
			;jmp	cs:fword ptr [irq_com3.irqOldOffset]
IntCom4:
			pushad
			mov	edi,OFFSET irq_com4		;point EDI at IRQDATA structure
			call	IntMain
			popad
			iret
			;jmp	cs:fword ptr [irq_com4.irqOldOffset]

			;--------------------------------------------------------------
			; Serial interrupt handler main body
			;--------------------------------------------------------------
IntMain:
			push	ds								;save registers
			push	es
			mov	ds,cs:[data_selector]	;get our DS
			ASSUME ds:_DATA

			IRQ_WATCH_ENTRY
;;; SPKR_SET ;*********************************************

			SAVE_EVENT IRQ_EVENT
			;--------------------------------------------------------------
			; Get pointer to PCSPAK structure, UART base address
			;--------------------------------------------------------------
			mov	esi,irqThis[edi]
			mov	dx,irqPort[edi]
			;--------------------------------------------------------------
			; Get status in al
			;--------------------------------------------------------------
procLoop:
			add	dx,UART_IIR					;point to interrupt ident reg
			in		al,dx							;get interrupt source ID
			sub	dx,UART_IIR					;restore dx
			;--------------------------------------------------------------
			; Receive character
			;--------------------------------------------------------------
			cmp	al,UART_IIR_RDR		 	;receiver ready?
			jne	short procNoRx				;no, skip it

			in		al,dx							;get character
			call	RecvChar						;process it
			jmp	short procLoop
procNoRx:
			;--------------------------------------------------------------
			; Send character
			;--------------------------------------------------------------
			cmp	al,UART_IIR_THR		 	;transmitter ready?
			jne	short procNoTx				;no, skip it

			call	SendChar						;send a character if available
			jmp	short procLoop
procNoTx:
			;--------------------------------------------------------------
			; Process modem status register change
			;
			; We currently ignore it.  We could someday implement
			; DTR/CTS/... flow control here if we desire.
			;--------------------------------------------------------------
			cmp	al,UART_IIR_MSR		 	;MSR change?
			jne	short procNoMSR			;no, skip it

			SAVE_EVENT IRQ_EVENT_MSR
			add	dx,UART_MSR					;read modem status reg to clear IRQ
			in		al,dx							;discard the result
			sub	dx,UART_MSR
			jmp	procLoop
procNoMSR:
			;--------------------------------------------------------------
			; Process line status register change (error or break)
			;--------------------------------------------------------------
			cmp	al,UART_IIR_LSR		 	;LSR change?
			jne	short procNoLSR			;no, skip it

			SAVE_EVENT IRQ_EVENT_LSR
			add	dx,UART_LSR					;read line status reg to clear IRQ
			in		al,dx							;read value
			sub	dx,UART_LSR

			and	ax,UART_LSR_ERRS			;remove non-error bits
			or		ErrorValue[esi],ax		;OR with current error state
			jmp	procLoop
procNoLSR:
			;--------------------------------------------------------------
			; Check for UART done
			;--------------------------------------------------------------
			cmp	al,001h						;if LSB is set, no interrupt
			je		short procDone
			SAVE_EVENT IRQ_EVENT_BOGUS
			DISABLE_AND_DIE 7					;weird state, flag error
procDone:
			;--------------------------------------------------------------
			; PIC port write
			; (allows future interrupts to occur)
			;--------------------------------------------------------------
			PIC_RESET_MASTER
			;--------------------------------------------------------------
			; Restore registers, return from SUBROUTINE (not interrupt!)
			;--------------------------------------------------------------
			IRQ_WATCH_EXIT
;;; SPKR_RESET ;*********************************************

			pop	es
			pop	ds
			ret

IntHandler  ENDP

END_CODE

	END
