Category : Assembly Language Source Code
Archive   : TSRCOMM.ZIP
Filename : TSRCOMM.ASM

 
Output of file : TSRCOMM.ASM contained in archive : TSRCOMM.ZIP

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TSRCOMM.ASM A Replacement for Interrupt 14
;;
;; Copyright 1987, Ross M. Greenberg
;;
;; This program is a terminate and stay resident program which allows
;; computers such as the IBM PC and compatibles using standard serial
;; communications calls to take advantage of the asynchronous interrupt
;; capabilities of the 8250 and 8259.
;;
;; The functionality with AH=0,1,2,3 remains the same as always
;;
;; With AH = 4, a new set of commands are now available.
;; Sub-functions are set in AL (See below for new function descriptions)
;;
;; To compile this program, you must have MASM 4.0 or a close relative,
;; and need only do:
;;
;; C> MASM TSRCOMM;
;; C> LINK TSRCOMM;
;; C> EXE2BIN TSRCOMM.EXE TSRCOMM.COM
;; C> DEL TSRCOMM.EXE
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; MACRO DEFINITIONS
;;
DOSINT macro set_ah_to
mov ah, set_ah_to
int 21h
endm
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; CONSTANTS
;;
FALSE equ 00h
TRUE equ 01h
BELL equ 07h
ONE_SECOND equ 18 ; roughly 18 timer ticks
TWO_SECONDS equ ONE_SECOND * 2 ; same idea



TIMER_TICK_INT_NO equ 1ch ; some might set this to 08h


;; Send an XOFF when you wish the remote to stop sending and
;; send an XON when the remote is allowed to continue
;;

XOFF equ 13h ; a control-S
XON equ 11h ; a control-Q



;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Define the size of the Communications Buffers
;;
;; You may want to change these to more accurately reflect your
;; actual needs. The high-water and low-water mark for XON/XOFF
;; processing are a function of the size of these two variables

P1_INLEN equ 400h
P2_INLEN equ 400h
P1_OUTLEN equ 400h
P2_OUTLEN equ 400h

;; Be careful with these settings if your have different lengths for each
;; of the COM_INBUF's: these only play off of COM1_INBUF

HIGH_MARK equ (P1_INLEN/10 * 8) ; send XOFF when buffer is
; 80% full
LOW_MARK equ (P1_INLEN/10 * 2) ; send XON when buffer is
; only 20% full
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Definitions of all of the 8250 Registers and their individual
;; bit meanings
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
DATA equ 0h ; DATA I/O is from the base

IER equ 1h ; Interrupt Enable Register
IER_RDA equ 1h ; Received Data Available interrupt bit
IER_THRE equ 2h ; Transmitter Holding Reg. Empty int bit
IER_RLS equ 4h ; Receive Line Status int bit
IER_MS equ 8h ; Modem Status int bit

IIR equ 2 ; Interrupt Identification Register
IIR_RLS equ 5h ; *equal* to if Receiver Line Status int
IIR_RDA equ 4h ; *equal* to if character ready
IIR_THRE equ 2h ; *equal* to if TX Buffer empty
IIR_PEND equ 1h ; set to zero if any interrupt pending
IIR_MS equ 0h ; *equal* to if Modem Status int

LCR equ 3h ; Line Control Register
LCR_WLS0 equ 0h ; Word Length Select Bit 0
LCR_WLS1 equ 1h ; Word Length Select Bit 1
LCR_STOPBITS equ 4h ; number of stop bits
LCR_PARITYEN equ 8h ; Enable Parity (see SPARITY & EPARITY)
LCR_EPARITY equ 10h ; Even Parity Bit
LCR_SPARITY equ 20h ; Stick Parity
LCR_BREAK equ 40h ; set if break is desired
LCR_DLAB equ 80h ; Divisor Latch Access Bit (baudrate setting)

MCR equ 4h ; Modem Control Register
MCR_DTR equ 1h ; Data Terminal Ready
MCR_RTS equ 2h ; Request To Send
MCR_OUT1 equ 4h ; Output 1 (nobody uses this!)
MCR_OUT2 equ 8h ; Out 2 (Sneaky Int enable in hware gates!)
MCR_LOOP equ 10h ; Loopback enable

LSR equ 5 ; Line Status Register
LSR_DATA equ 1h ; Data Ready Bit
LSR_OVERRUN equ 2h ; Overrun Error Bit
LSR_PARITY equ 4h ; Parity Error Bit
LSR_FRAMING equ 8h ; Framing Error Bit
LSR_BREAK equ 10h ; Break Detect (sometimes an error!)
LSR_THRE equ 20h ; Transmit Holding Register Empty
LSR_TSRE equ 40h ; Transmit Shift Register Empty

MSR equ 6 ; Modem Status Register
MSR_DEL_CTS equ 1h ; Delta Clear To Send
MSR_DEL_DSR equ 2h ; Delta Data Set Ready
MSR_EDGE_RI equ 4h ; Trailing Edge of Ring Indicator
MSR_DEL_SIGD equ 8h ; Delta Receive Line Signal Detect (delta DCD)
MSR_CTS equ 10h ; Clear To Send
MSR_DSR equ 20h ; Data Set Ready
MSR_RI equ 40h ; Ring Indicator - during the entire ring
MSR_DCD equ 80h ; Data Carrier Detect - Someone On-line
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; The 8259 interrupt controller is at port I/O INT_CTRL
;; To reset from an interrupt, you must output an INT_EOI.
;;
;; To enable or disable interrupts for either of the com ports, read
;; the value on port INT_MASK_PORT, turn the appropriate COM1 or COM2 bit
;; on or off, and output the new mask back out the port.
;;
;;
CTRL_PORT equ 20h
INT_EOI equ 20h
;;
INT_MASK_PORT equ 21h
COM1_MASK equ 0efh
COM2_MASK equ 0f7h
INTNO_COM1 equ 0ch
INTNO_COM2 equ 0bh
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; These ports may or may not respond to the ports at base 40:0.
;; If they don't we'll refuse to run. Not at all subtle, but effective.
;; See the first Sidebar for more information
;;
PORT1 equ 3f8h
PORT2 equ 2f8h
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


code segment para ; align the code segment to the nearest
; paragraph boundary
assume cs:code
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
org 2ch
env_ptr label word ; this points to the environment address
; as stored in the PSP. We'll free this
; in the startup routine.

org 100h ; COM programs always start at 100h
; just like CP/M!

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; START OF TSR CODE
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
start: jmp install



;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; COMM PORT BLOCK (CPB)
;;
;; Comm Port Block defines information unique for each comm port
;; and includes information such as what the original interrupt
;; vector pointed to, which parameters are set, etc.
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
CPB struc

cpb_base dw ? ; the base port of the comm port (2F8, 3F8)
cpb_nint_off dw ? ; new interrupt offset address
cpb_pic_mask db ? ; mask for enabling ints from 8259
cpb_int_no db ? ; what interrupt we are

cpb_mode dw ? ; whatever modes we have turned on
cpb_timeout dw ? ; individual timeout value off timer tick
cpb_in_xoff dw 0 ; true if we output an XOFF
cpb_out_xoff dw 0 ; true if an XOFF was sent to us

cpb_inbase dw ? ; start of input buffer
cpb_inlen dw ? ; total length of input buffer allocated
cpb_inhead dw ? ; pointer to next input char location
cpb_intail dw ? ; pointer to last input char location
cpb_incnt dw 0 ; count of how many inp characters outstanding
cpb_inerrors dw ? ; pointer to the error bits

cpb_outbase dw ? ; start of output header
cpb_outlen dw ? ; total length of output buffer allocated
cpb_outhead dw ? ; pointer to next output char location
cpb_outtail dw ? ; pointer to last output char location
cpb_outcnt dw 0 ; count of how many outp characters outstanding
cpb_outend dw ? ; ptr to the end of the output buffer

cpb_tx_stat dw 0 ; set to no interrupts turned on
cpb_oint_add dw ? ; original interrupt offset:segment order
dw ?
CPB ends
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; HANDSHAKING OPTIONS
;; Used for AH=4, AL =1. Set CX as a combination of the desired options
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
BREAK_IS_ERROR_OPTION equ 01h
DSR_INPUT_OPTION equ 02h
DCD_INPUT_OPTION equ 04h
CTS_OUTPUT_OPTION equ 08h
XOFF_INPUT_OPTION equ 10h
XOFF_OUTPUT_OPTION equ 20h
DTR_OPTION equ 40h
XON_IS_ANY_OPTION equ 80h
TX_INTS_ON_OPTION equ 100h

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; DEFAULT MODE
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
DEF_MODE equ DSR_INPUT_OPTION + CTS_OUTPUT_OPTION + XON_IS_ANY_OPTION

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; DEFAULT TIME_OUT INTERVAL, FLAGS, BAUD_RATES, and other various
;; denizens.
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
DEF_TIME equ TWO_SECONDS

DEF_FLAGS equ 0
DEF_T_OUT equ 0

DEF_BAUD equ 0e0h ;default baud is 9600
DEF_PARITY equ 0h ;default parity is off
DEF_STOP equ 0h ;defualt stop bits is 1
DEF_WORDLEN equ 3h ;default wordlength is 8

DEF_INIT equ DEF_BAUD + DEF_PARITY + DEF_STOP + DEF_WORDLEN


NO_XON equ FALSE
NO_CHARS equ 0

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Masm has a 256 byte static initialization limit. NC is shorter than
;; NO_CHARS....
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
NC equ 0

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; GLOBAL DATA ALLOCATIONS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
old_int dw ? ; used as a temporary vector for 'weird' ints
dw ?

orig_timer dw ? ; the original timer vector
dw ?

special_return_value dw 0ff0h
timed_out dw 0 ; "any time outs?" flag

my_timer dw ? ; timer for local usage
timer_tick dw ? ; this value used for timer countdown
old_int14 dw ? ; the original int14 vector
dw ?

error_flag dw 0 ; used as a place holder in the RDA routine

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Allocate space for both com ports input and output buffers
;; and the input error bits array
;;
;; WARNING! Do not move the error array away from its approriate
;; error array, or you'll probably crash at some point!
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
com1_inbuf db P1_INLEN dup(0)
com1_errs db (P1_INLEN/8) + 1 dup(0)

com2_inbuf db P2_INLEN dup(0)
com2_errs db (P2_INLEN/8) + 1 dup(0)

com1_outbuf db P1_OUTLEN dup(0)
com2_outbuf db P2_OUTLEN dup(0)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; CPB1 and CPB2
;;
;; Allocate space and initialize the COMM PORT BLOCKS for com1 and com2
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
cpb1 CPB
cpb2 CPB


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; EMASK_BYTE
;;
;; Input Parameters:
;; Pointer to input buffer in BX
;;
;; Returns:
;; Pointer to correct byte in error array in BX,
;; with the correct bit mask in AX
;;
;; All other Registers Preserved
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
emask_byte proc near
assume ds:code

push cx
push dx

sub bx, cpb_inbase[si] ; get the actual offset
mov ax, bx ; get ready to divide the offset
cwd ; (make a double word out of it)
mov cx, 8
div cx ; number of bits to a word
add ax, cpb_inerrors[si] ; ax now points to the proper byte
mov bx, ax
; in the error array
mov cx, dx ; get ready to shift the remainder
; to make the mask
mov ax, 1 ; make the temporary mask
shl ax, cl ; and shift it over

pop dx
pop cx
ret
emask_byte endp

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; INIT_BUFFERS
;;
;; Input Parameters
;; Pointer in SI to current CPB
;;
;; All Registers Preserved
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

init_buffers proc near
push ax
mov ax, cpb_inbase[si] ; clear the input buffer
mov cpb_inhead[si], ax
mov cpb_intail[si], ax
mov cpb_incnt[si], NO_CHARS

mov ax, cpb_outbase[si] ; and the output buffer
mov cpb_outhead[si], ax
mov cpb_outtail[si], ax
mov cpb_outcnt[si], NO_CHARS
mov cpb_outend[si], ax
mov ax, cpb_outlen[si]
add cpb_outend[si], ax
pop ax
ret
init_buffers endp

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TX_ON
;;
;; Input Parameters
;; SI points to current CPB, DI to current Base Port.
;; This routine enables Transmit Buffer Empty Interrupts
;;
;; All Registers Preserved
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
tx_on proc near
assume ds:code

cmp cpb_tx_stat[si], TRUE ; are tx ints already enabled?
jz tx_on2 ; yes

test cpb_mode[si], TX_INTS_ON_OPTION
jz tx_on2 ; don't turn ints on if not needed

mov cpb_tx_stat[si], TRUE ; mark interrupts as enabled
push ax
push dx
lea dx, IER[di] ; take a look at the Interrupt
in al, dx ; enable register

or al, IER_THRE ; turn on the interrupt bit
out dx, al ; and turn it on in the chip
pop dx
pop ax

tx_on2:
ret ; ints will be turned on eventually
tx_on endp



;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TX_OFF
;;
;; Input Parameters
;; SI points to current CPB, DI to current Base Port.
;; This routine disables Transmit Buffer Empty Interrupts
;;
;; All Registers Preserved
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
tx_off proc near
assume ds:code

cmp cpb_tx_stat[si], FALSE ; are tx ints already disabled?
jz tx_off2 ; yes

test cpb_mode[si], TX_INTS_ON_OPTION
jz tx_off2 ; shouldn't be on in the first place

mov cpb_tx_stat[si], FALSE ; mark interrupts as disabled
push ax
push dx
lea dx, IER[di] ; take a look at the Interrupt
in al, dx ; enable register

and al, NOT IER_THRE ; turn off the interrupt bit
out dx, al ; and turn it on in the chip
pop dx
pop ax

tx_off2:
ret ; ints will be turned on eventually
tx_off endp


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; GET_COM_PORT
;;
;; Input Parameters
;; SI points to current CPB
;;
;; Returns proper comm port (0 or 1) in DX. All other registers preserved
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
get_com_port proc near
assume ds:code

cmp si, offset cpb1
jnz not_com1
mov dx, 0
jmp ret_com_port

not_com1:
cmp si, offset cpb2
jnz not_com2
mov dx, 1
jmp ret_com_port

not_com2:
mov dx, 0ffh

ret_com_port:
ret
get_com_port endp

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; GET_CPB
;;
;; Input Parameters
;; Comm port (0 or 1) in DX
;;
;; Returns pointer to CPB in SI
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
get_cpb proc near
assume ds:code

cmp dx, 0
jnz not_comm1
mov si, offset cpb1
jmp ret_cpb

not_comm1:
cmp dx, 1
jnz not_comm2
mov si, offset cpb2
jmp ret_cpb

not_comm2:
mov si, 0

ret_cpb:
ret
get_cpb endp



;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; SEND_CHAR
;;
;; Input Parameters
;; Character to be sent in al, pointer to current CPB in SI,
;; Base port in DI
;;
;; All Registers Preserved
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
send_char proc near

push dx

call get_com_port
call out_char

pop dx
ret

send_char endp


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; OUT_BELL
;; SEND_XOFF
;; SEND_XON
;;
;;
;; Input Parameters
;; None
;;
;; Sends BELL out the comm port
;; " XON " " " "
;; " XOFF " " " "
;;
;; All Registers Preserved
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
out_bell proc near
assume ds:code

push ax
mov al, BELL
call send_char
pop ax
ret

out_bell endp


send_xoff proc near
assume ds:code
push ax
mov al, XOFF
call send_char
pop dx
ret
send_xoff endp

send_xon proc near
assume ds:code
push ax
mov al, XON
call send_char
pop dx
ret
send_xon endp


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TILL_CLEAR
;;
;; Input Parameters
;; Before affecting the transmit interrupts, this routine
;; allows the Shift Register to clear, or for timeouts to
;; run out before returning. DI should point to the Base
;; Port of the comm port.
;;
;; Nothing Returned, all registers preserved
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
till_clear proc near

push dx
push ax

mov my_timer, TWO_SECONDS ; for no more than one second
lea dx, LSR[di] ; better check till we're really done
lp1:
in al, dx

test al, LSR_TSRE ; check the shift register
jnz end_lp1 ; done, turn off ints

cmp my_timer, 0 ; compare the timer tick to zero
jnz lp1 ; we'll just give up eventually
end_lp1:
pop ax
pop dx
ret
till_clear endp

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; OUT_CHAR
;;
;; Input Parameters
;; AL should hold the character to be outputted, SI points
;; to the CPB and DI to the base register.
;;
;; All of the various handshaking parameters are checked and
;; acted upon. If a timeout occurs, the timed_out counter
;; will be incremented
;;
;; Returns AH=80h if timed out
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
out_char proc near

push ax
mov bx, cpb_timeout[si] ; get the timeout
mov timer_tick, bx ; and stick into the autodecrementer

top_loop:
lea dx, MSR[di] ; check the status of the serial port
in al, dx

test cpb_mode[si], DSR_INPUT_OPTION
jz no_dsr_test ; not dsr handshaking
test al, MSR_DSR ; we're handshaking...hows it look?
jz loop_it ; not yet

no_dsr_test:
test cpb_mode[si], CTS_OUTPUT_OPTION
jz no_cts_test ; no cts handshaking
test al, MSR_CTS ; we're handshaking...hows it look?
jz loop_it ; not yet

no_cts_test:
cmp cpb_out_xoff[si], FALSE ; in an XOFF state?
jnz loop_it ; yes, so cycle. It may end

lea dx, LSR[di] ; finally, check the xmit buffer
; on the chip
chip_check:
in al, dx ; get the status
test al, LSR_THRE ; clear to send it?
jnz squirt_it ; yes! send the character

loop_it:
cmp timer_tick, 0 ; out of time yet?
jnz top_loop ; not yet, so cycle

pop ax ; keep the stack clean
or ah, 080h ; mark a timeout
inc timed_out
ret

squirt_it:
lea dx, DATA[di] ; get the port to squirt from
pop ax ; retrieve the original character
out dx, al ; and squirt it.
xor ah, ah
ret
out_char endp



;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ASYNC SUB-SERVICE ROUTINES
;;
;; These routines are only entered by the COMMON_ISR routine, which
;; validates there is a valid interrupt on the aproriate comm port,
;; and sets up SI to point to the correct CPB and DI to the correct
;; Base Port.
;;
;; COMMON_ISR also preserves all registers and the flags, and resets
;; the EOI required at the end of each interrupt.
;;
;; Finally, COMMON_ISR is, itself, called either by COM1_ISR or COM2_ISR
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; MS_INT
;;
;; MODEM STATUS INTERRUPT
;; This interrupt should never happen. If it does, clear the status
;; register and return.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
ms_int proc near
assume ds:code

lea dx, MSR[di]
in al, dx
ret

ms_int endp

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; XMIT_INT
;;
;; TRANSMITTER EMPTY INTERRUPT
;; This interrupt is generated whenever interrupts are TX interrupts
;; are turned on and the Transmiter Holding Register is empty.
;;
;; If there are more characters to be transmitted, then load up
;; another one and move the pointers. If there are no more characters
;; to be transmitted, then wait until the the last character has been
;; transmitted (the Shift Register must be empty), then turn TX
;; interrupts off. This gives a slight delay on the last character
;; but guarantees the last character will be properly transmitted.
;;
;; If XON/XOFF processing on outgoing characters is enabled, then if we
;; get a TX int with the XOFF flag set (indicating we had gotten an XOFF
;; in the RX routine earlier), we'll simply shut off TX interrupts as if
;; we had an empty TX buffer. Subsequent characters received in the RX
;; routine will reset the XON/XOF flag and turn TX interrupts on as well.
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

xmit_int proc near
assume ds:code
cmp cpb_outcnt[si], 0 ; anywork to do?
jnz xmit1 ; yep!

call till_clear ; wait for transmitter to clear

call tx_off ; turn xmit interrupts off

ret ; and return

xmit1:
mov bx, cpb_outtail[si] ; get the next character to xmit
inc bx ; now point right on it
cmp bx, cpb_outend[si] ; cmp to the end
jnz xmit2 ; if not past the end, jump
mov bx, cpb_outbase[si] ; past the end, reset to the head

xmit2:
cli ; don't get interrupted now
dec cpb_outcnt[si] ; decrement the count of chars to go
mov cpb_outtail[si], bx ; and save a pointer to the next char
mov al, [bx] ; get the character in al

cmp cpb_outcnt[si], 0 ; any work left to do?
jnz out_it ; yep!

call till_clear ; wait for transmitter to clear
call tx_off ; turn xmit interrupts off

out_it:
call out_char ; output the character
ret
xmit_int endp

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; RDA_INT
;;
;;
;; CHARACTER READY INTERRUPT
;; This interrupt is generated whenever a character is ready on the
;; serial port.
;;
;; If the "DSR must be set" option is turned on, and DSR isn't, then
;; the character is read, then discarded. Likewise for the "DCD must
;; be high" option.
;;
;; If there is room in the buffer, add it and increment the pointers.
;; If there isn't room, then clear the character and discard it, then
;; generate a BELL character out the port (in case a human were
;; listening).
;;
;; After adding the character, if XON/XOFF processing of incoming
;; characters is enabled, the HIGH_WATER level is checked. If the
;; character count exceeds it, then if the XOFF flag isn't set, an
;; XOFF character is sent and the XOFF flag is incremented. The XON
;; is sent (and the XOFF flag reset) in the "get-a-character" routine
;; in the interrupt 0x14 replacement when the low water mark is reached.
;;
;; When adding a character, the error status is checked (if the
;; check_flag is set in the cpb) If an error exists on the port
;; the corresponding bit is set in the error buffer for the com port.
;; This is later examined in the "get-a-character" routine and the
;; "get-status" routine. A generalized error is returned if the error
;; bit is set.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
rda_int proc near
assume ds:code

test cpb_mode[si], DSR_INPUT_OPTION + DCD_INPUT_OPTION
; is the DSR or DCD option turned on?
jz rda_2 ; nope
lea dx, MSR[di] ; check the modem status
in al, dx
test cpb_mode[si], DSR_INPUT_OPTION
jz just_dcd ; just check DCD
test al, MSR_DSR ; is it set?
jz rda_1 ; nope. discard.

just_dcd:
test cpb_mode[si], DCD_INPUT_OPTION
jz rda_2 ; get the character
test al, MSR_DCD ; is there carrier?
jnz rda_2 ; you bet! get the character

rda_1: ; read the character, discard by
; returning
lea dx, DATA[di]
in al, dx
ret

rda_2:
lea dx, LSR[di] ; reading status reg clears errors
in al, dx
test al, LSR_OVERRUN + LSR_PARITY + LSR_FRAMING
jz no_err1 ; any of the above errors? No.
mov error_flag, TRUE ; set the error_flag
jmp get_char

no_err1: ; break an error?
test cpb_mode[si], BREAK_IS_ERROR_OPTION
jz get_char ; nope
test al, LSR_BREAK ; is break on?
jz get_char ; nope
mov error_flag, TRUE ; yep. Set the error

get_char:
lea dx, DATA[di] ; get the data
in al, dx

cmp error_flag, FALSE ; any errors?
jnz insert1 ; yep, so skip the XON/XOFF stuff

; no errors, so XON/XOFF would be
; valid

test cpb_mode[si], XOFF_OUTPUT_OPTION
jz insert1 ; skip if output xoff not turned on
cmp al, XOFF ; is this an XOFF character?
jnz insert0 ; nope
mov cpb_out_xoff[si], TRUE ; turn this on for the next TX int
call tx_off ; turn off tx interrupts
ret ; and return

insert0:
cmp cpb_out_xoff[si], FALSE ; are we in XOFF mode?
jz insert1 ; no, so insert the character

; do we care about exact matching only?
test cpb_mode[si], XON_IS_ANY_OPTION
jz any_char ; no. Any character will do.

cmp al, XON ; is this an XON character?
jz any_char ; yes, so turn things back on

ret ; XOFF is on, this isn't an XON,
; so simply ignore it
any_char:
mov cpb_out_xoff[si], FALSE ; turn this off and set up for
call tx_on ; the next TX int
ret ; and return


insert1:
; send an XOFF quick as you can if
; needed
test cpb_mode[si], XOFF_INPUT_OPTION
jz insert2 ; no XOFF processing required
cmp cpb_incnt[si], HIGH_MARK; do we need an XOFF?
jle insert2 ; no XOFF required right now

cmp cpb_in_xoff[si], FALSE ; have we already sent one?
jnz insert2 ; yes

mov cpb_in_xoff[si], TRUE ; set the flag and
call send_xoff ; send it

insert2:
mov bx, cpb_inhead[si]
inc bx ; move the head up by one
cmp bx, cpb_inerrors[si] ; have we gone past the end?
jnz insert3 ; nope, so stick it in the buffer
mov bx, cpb_inbase[si] ; yep, so start it over

insert3:
cmp bx, cpb_intail[si] ; are we sneaking up on ourselves?
jnz insert4 ; nope, just insert it
call out_bell ; yep! ring a bell, and
mov error_flag, TRUE ; set an error on the next character
sti
ret ; and simply return

insert4:
mov [bx], al ; put the character in its place and
cli ; turn ints off for a little while
mov cpb_inhead[si], bx ; preserve the new value of the head
inc cpb_incnt[si] ; and mark how many characters exist

sti
cmp error_flag, FALSE ; any errors?
jz rda_return ; no

call emask_byte ; get the byte and the mask
or [bx], ax ; and make the bit turn on
mov error_flag, FALSE ; and turn them of for the next time

rda_return:
ret
rda_int endp

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ERR_INT
;;
;; ERROR INTERRUPT
;;
;; This interrupt should never happen. If it does, read the
;; register and return.
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
err_int proc near
assume ds:code

lea dx, LSR[di] ; get the status word
in al, dx
iret

err_int endp

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; This is a table containing the offsets for each of the expected
;; interrupts from the 8250. Interesting how the offsets off the IIR
;; happen to be by two. Great for offseting into this table!
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
interrupt_table label word ;;
dw offset ms_int ;; modem status interrupt
dw offset xmit_int ;; transmitter interrupt
dw offset rda_int ;; character ready interrupt
dw offset err_int ;; receiver line error interrupt
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; COM1_ISR AND COM2_ISR
;;
;; These are the entry points for each of the COMM Interrupts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
com1_isr proc far
assume ds:nothing
push ax
lea ax, cpb1
jmp short common_isr
com1_isr endp

com2_isr proc far
assume ds:nothing
push ax
lea ax, cpb2
jmp short common_isr
com2_isr endp


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; COMMON_ISR
;;
;; A common entry point into the "save the registers and call the right
;; interrupt service routine".
;;
;; A little twist: if it does not appear the interrupt came from a
;; source we expect, then jump to the old interrupt vector
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
common_isr proc near
assume ds:nothing

push bx
push cx
push dx
push si
push di
push ds

push cs ;addressing off ds as cs
pop ds
assume ds:code ;makes it easier to think


mov si, ax ; move in the cpb
mov di, cpb_base[si] ; get the base port
lea dx, IIR[di] ; and then the interrupt ID Register
in al, dx ; get the interrupt type
test al, IIR_PEND ; is there a pending interrupt?
jz is_mine ; interrupt on *this* chip!

other_int:
cli ; turn off interrupts since this
; is non-reentrant
mov ax, cpb_oint_add[di] ; grab the old interrupt out of
mov old_int, ax ; the structure
mov ax, cpb_oint_add[di][2]
mov old_int[2], ax

pop ds ; pop everything back
assume ds:nothing
pop di
pop si
pop dx
pop cx
pop bx
pop ax
jmp dword ptr cs:[old_int] ; jump to whatever was there

polling_loop: ; this is a requirement to be sure
; we haven't lost any any interrupts

lea dx, IIR[di] ; load the interrupt ID Register
in al, dx ;
test al, IIR_PEND ; is there a pending interrupt?
jnz clear ; no. time to return

is_mine:

and ax, 06h
mov bx, ax
mov bx, interrupt_table[bx]

push di ; save di for the polling loop
call bx
pop di

jmp polling_loop ; time to check for more work

clear: ; no further interrupt processing
pop ds ; pop everything back
assume ds:nothing
pop di
pop si
pop dx
pop cx
pop bx

cli ; interrupts off, then reset
mov al, INT_EOI ; interrupts on the 8259
out CTRL_PORT, al
no_eoi:
pop ax


iret ; the iret will turn interrupts back on
common_isr endp

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; NEW_14
;;
;; The replacement for the interrupt 14 service routines
;; Services 0,1,2,3 are basically the same as before the vector
;; was stolen.
;;
;; This routine sets up SI and DI to point to the correct CPB and
;; Base Port. then vecots to the appropriate service routine
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
new_14 proc far
assume ds:nothing

sti
;push everything in sight,
; *except* ax
push bx
push cx
push dx
push di
push si
push bp
push ds

push cs ;addressing off ds as cs
pop ds
assume ds:code ;makes it easier to think

call get_cpb ; get si to point to the proper cpb
mov di, cpb_base[si] ; get di to point to the base port

cmp ah, 4 ; better check for valid function
jle ok_func ; function is okay
mov ax, 0ffffh ; set up as a bad code
jmp return ; and return

ok_func:
mov bl, ah
xor bh, bh
shl bx, 1 ; make an offset into the table
mov bx, int14_functions[bx] ; and get the address of the function
call bx

return:
pop ds
assume ds:nothing
pop bp
pop si
pop di
pop dx
pop cx
pop bx

iret
new_14 endp



;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
int14_functions label word ;;
dw offset init14 ;; initialize the port
dw offset send14 ;; send the character in al
dw offset get14 ;; return next charaacter in al, status
;; in ah
dw offset stat14 ;; get serial status, return in ax
dw offset newfuncs14 ;; all of the new functions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; INIT14
;;
;; Input Parameters
;; The topmost three bits (7,6,5) contain the baudrate:
;; 000 - 110
;; 001 - 150
;; 010 - 300
;; 011 - 600
;; 100 - 1200
;; 101 - 2400
;; 110 - 4800
;; 111 - 9600
;;
;; The next two bits (4,3) contain the parity:
;; 00 - None
;; 01 - Odd
;; 10 - None
;; 11 - Even
;;
;; The next bit (2), if set, indicates two stopbits, otherwise
;; one stopbit
;;
;; The final two bits (1,0) are the word length:
;;
;; 00 - Undefined
;; 01 - Undefined
;; 10 - 7 Bits
;; 11 - 8 Bits
;;
;; This routine is very similar to the original BIOS routine, with
;; the exception that it gets a divisor for determining the baud
;; rate which exceeds the 9600 original in the BIOS. When the
;; comm port is initialized through the old interrupt with AH=0,
;; it is the same as the old, except for the mode and timeout reset.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
init14 proc near
assume ds:code

push ax
lea dx, LCR[di] ; get the Latch
in al, dx
or al, LCR_DLAB ; turn on the divisor
out dx, al ; in the chip
pop ax ; get the old ax back

push ax ; and save it
mov cl, 5 ; we'll shift it over to
shr ax, cl ; make a table offset of it

call get_baud ; then get the correct divisor
; allows higher than 9600

lea dx, DATA[di] ; get the base address
out dx, ax ; output the whole word

pop ax ; get back original ax
lea dx, LCR[di] ; get the Latch
and al, 01fh ; just the parity, stop bits,
; word length
out dx, al ; set the params

mov cpb_mode[si], DEF_MODE ; default modes
mov cpb_timeout[si], DEF_TIME ; default timeout period

jmp stat14 ; the old one returned the
; status in al
init14 endp

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; GET_BAUD
;;
;; Input Parameters
;; AX should be the ofset into the baudrate table.
;; The first eight entries are exactly as the BIOS has them
;; The others were extrapolated for 19200, and 38400 baud
;;
;; Returns the divisor in AX
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
get_baud proc near
assume ds:code

shl ax, 1 ; make the table offset
push bx
mov bx, ax
mov ax, baudrate_table[bx] ; and get the divisor
pop bx
ret

get_baud endp

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; These numbers are in decimal. As can be seen, to double the baudrate
;; the divisor gets halved. So....what comes after 384000??
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
baudrate_table label word
dw 1047 ; 110 baud
dw 768 ; 150 baud
dw 384 ; 300 baud
dw 192 ; 600 baud
dw 96 ; 1200 baud
dw 48 ; 2400 baud
dw 24 ; 4800 baud
dw 12 ; 9600 baud
dw 6 ; 19200 baud
dw 3 ; 38400 baud
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; SEND14
;;
;; This routine will attempt to send the character in al for the
;; entire timeout period if the TX_INTS_ON_OPTION is off.
;;
;; If the option is on, it will queue the item, instead, into the
;; queue, and let the interrupt system handle it from there.
;;
;; This routine will return the status of the send (if any) in AX
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
send14 proc near
assume ds:code

test cpb_mode[si], TX_INTS_ON_OPTION
jz just_squirt ; the option isn't being
; used, so send it the old way

mov bx, cpb_outhead[si] ; get the current head
inc bx ; move it forward by one
cmp bx, cpb_outend[si] ; cmp to the end
jnz send2 ; if not past the end, jump
mov bx, cpb_outbase[si] ; past the end, reset to the head

send2:
cmp bx, cpb_outtail[si] ; about to clobber the tail?
jz junk_char ; yep! throw this character away!

mov [bx], al ; stick the character in the queue
cli ; turn off interrupts
mov cpb_outhead[si], bx ; and save the new value
inc cpb_outcnt[si] ; and increment the output char cnt
call tx_on

junk_char:
sti
ret

just_squirt:
call out_char

jmp stat14 ; then get the status

send14 endp

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; GET14
;;
;; This routine is a replacement for the AH = 2 get-a-character
;; routine in the BIOS.
;;
;; Input Parameters
;; Set the COM port in DX (0 or 1), Base in DI, CPB in SI
;;
;; Returns character (if any) in AL, with partial comm port status
;; in AH. Check the high bit ( & 0x80) to determine timeout,
;; or if AH is non zero it is safe to assume some error has
;; occurred
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
get14 proc near
assume ds:code

mov ax, cpb_timeout[si]
mov timer_tick, ax ; in case there is no character ready,
; set the timeout count in advance


try_again:
cmp cpb_incnt[si], 0 ; any characters out there?
jnz got_one ; yep!

sti ; make sure interrupts are on while
cmp timer_tick, 0 ; waiting for the timer to zero out
jnz try_again
or ah, 080h ; set the timeout error bit
inc timed_out ;
ret


got_one:
mov bx, cpb_intail[si] ; get the tail
inc bx ; point to next character
cmp bx, cpb_inerrors[si] ; past end?
jnz get_char1 ; no
mov bx, cpb_inbase[si] ; yes, so reset to beginning

get_char1:
mov al, [bx] ; get the actual character
xor ah,ah
push ax ; save it
push bx
call emask_byte ; to check if an error
test [bx], ax ; is there an error?
pop bx
pop ax

jz no_inp_error ; no error on input
; there was an error or some kind,
; so turn on all error bits, including
; break if the flag is set.

or ah, LSR_OVERRUN + LSR_PARITY + LSR_FRAMING
test cpb_mode[si], BREAK_IS_ERROR_OPTION
jz no_inp_error ; nope
or ah, LSR_BREAK ; is break on?

no_inp_error:
cli ; no interruptions
dec cpb_incnt[si] ; decrease the character count
mov cpb_intail[si], bx ; and save the next character ptr

test cpb_mode[si], XOFF_INPUT_OPTION
jz skip_xon ; do an XON need to be sent?
cmp cpb_in_xoff[si], 0 ; currently in xoff?
jz skip_xon ; no
cmp cpb_incnt[si], LOW_MARK ; down low enough for a cushion?
ja skip_xon ; no
mov cpb_in_xoff[si], FALSE ; turn off XOFF flag
call send_xon ; and tell remote to continue sending


skip_xon:
sti ; and turn interrupts on again

ret

get14 endp

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; STAT14
;;
;; Input Parameters
;; Set the COM port in DX (0 or 1), Base in DI, CPB in SI
;;
;; This routine will return the status of the comm port in AX. A
;; bit of fudging is done, since the low level interrupt driver may be
;; handling the XON/XOFF status. General logic of the routine:
;;
;; Starting with the real status of the device (from the LSR register),
;; turn on the character ready bit if there are any characters in the
;; low level receive buffer. If there is a character in the buffer,
;; determine if there is an error waiting for the next read. If there
;; is, then set all error conditions, including BREAK (if break is
;; considered an error condition).
;;
;; Next, get the modem status, and then check out the XOFF status. If
;; XOFF is set for the outgoing line, make it appear the Transmit Shift
;; and Hold Registers are still busy. If the option is set, OR the
;; DTR/DSR bit to show the "terminal" is ready.
;;
;; Finally, if there is a time out (and timed_out) is a counter, then
;; set the TIMED_OUT flag, bit 7 of the high byte.
;;
;; Return in AX
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
stat14 proc near

lea dx, LSR[di] ; try the *real* status register
in al, dx
xor ah, ah

cmp cpb_incnt[si], 0 ; any characters waiting in the buffer?
jz no_brk_error ; no.
or ax, LSR_DATA ; set the bit if not already set.

; now see if the next char in the
; buffer will bring an error with it

push ax
push bx
mov bx, cpb_intail[si] ; get the ptr to the next character
call emask_byte ; get the error status byte and mask
test [bx], ax ; and see if an error condition
pop bx
pop ax

jz no_brk_error ; no errors
or ax, LSR_PARITY + LSR_FRAMING + LSR_OVERRUN
test cpb_mode[si], BREAK_IS_ERROR_OPTION
jz no_brk_error ; break is not posible cause of
; error
or ax, LSR_BREAK ; break considered an error, set bit

no_brk_error:
mov ah, al ; now get the actual line status in al
lea dx, MSR[di] ; modem status register
in al, dx

; even if DSR is low, if set to ignore
; its state, set status high

cmp cpb_out_xoff[si], FALSE ; is state currently in XOFF on output?
jz dsr_stat ; no XOFF worries, see about DSR

; XOFF is set, which should look like
; a device not ready. Fake it out.
and al, not LSR_TSRE + LSR_THRE

dsr_stat:
test cpb_mode[si], DTR_OPTION
jz no_dsr_concern ; nope
or al, MSR_DSR ; set the status bit

no_dsr_concern:

cmp timed_out, FALSE ; any faked time outs?
jz no_time_outs ; no time_outs;
dec timed_out ; start zeroing it for next time
or ah, 080h ; and set the time out flag

no_time_outs:
ret
stat14 endp

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; New Functions: All of these functions require that AH = 4, and
;; the sub function can be found in AL as follows:
;;
;; if AL = 0, return 0ff0 in AX to determine load status
;; (actual value in 'special_return_value')
;;
;; if AL = 1, initialize mode as per CX, clear buffers.
;;
;; if AL = 2, then initialize with baudrates and other
;; params in CL, as if AL of AH = 0 init
;; BIT 6 & 5 ==> Baudrates
;; 0 , 1 ==> 19,200
;; 1 , 0 ==> 38,400
;; BIT 4 & 3 ==> Parity
;; BIT 2 ==> Stop Bits
;; BIT 1 & 0 ==> Word Length
;;
;; if AL = 3, set the timeout value as per CX
;;
;; if AL = 4, Clear the Input Buffer
;;
;; if AL = 5, Return count in Input buffer
;;
;; if AL = 6, Clear the Transmit Buffer
;;
;; if AL = 7, Return the count in the Transmit Buffer
;;
;; if AL = 8, uninstall the TSR driver, then release
;; memory
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
funcs_table label word ;;
dw offset new00 ;; Each function corresponds to the
dw offset new01 ;; AL value used for the sub-function
dw offset new02 ;;
dw offset new03 ;;
dw offset new04 ;;
dw offset new05 ;;
dw offset new06 ;;
dw offset new07 ;;
dw offset new08 ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; NEWFUNCS14
;;
;; The new function dispatcher. Checks to make sure AL is valid and
;; returns 0xffff if not.
;;
;; If AL is valid, then call the proper routine.
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
newfuncs14 proc near
assume ds:code

cmp al, 08h ; out of bounds?
jle dispatch ; no
mov ax, 0ffffh ; yes, error code
ret

dispatch:
call get_cpb ; get si to point to the proper cpb
mov di, cpb_base[si] ; point the ports!
xor bx, bx
mov bl, al
shl bx, 1
mov bx, funcs_table[bx]
call bx
ret
newfuncs14 endp

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; if AL = 0, return 0ff0 in AX to determine load status
;; (actual value in 'special_return_value')
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
new00 proc near

mov ax, special_return_value
ret
new00 endp

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; if AL = 1, initialize mode as per CX, clear buffers.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
new01 proc near

mov cpb_mode[si], cx ; move the new mode in
call init_buffers ; and reset the pointers
ret
new01 endp

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; if AL = 2, then initialize with baudrates and other
;; params in CL, as if AL of AH = 0 init
;; BIT 6 & 5 ==> Baudrates
;; 0 , 1 ==> 19,200
;; 1 , 0 ==> 38,400
;; BIT 4 & 3 ==> Parity
;; BIT 2 ==> Stop Bits
;; BIT 1 & 0 ==> Word Length
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
new02 proc near

lea dx, LCR[di] ; get the Latch
in al, dx
or al, LCR_DLAB ; turn on the divisor
out dx, al ; in the chip

push cx
mov ax, cx

and ax, 00e0h ; only the highest three bits
mov cl, 5
shr ax, cl
add ax, 7 ; makes offset start at 8
; (19200)

call get_baud ; then get the correct divisor
; allows higher than 9600
pop cx

lea dx, DATA[di] ; get the base address
out dx, ax ; output the whole word

lea dx, LCR[di] ; get the Latch
mov al, cl ; get the other parameters and
and al, 01fh ; mask only parity, stop bits,
; word length
out dx, al ; set the params

ret
new02 endp

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; if AL = 3, set the timeout value as per CX
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
new03 proc near

mov cpb_timeout[si], cx
ret
new03 endp

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; if AL = 4, Clear the Input Buffer
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
new04 proc near

cli
mov cpb_incnt[si], NO_CHARS
mov ax, cpb_inbase[si]
mov cpb_inhead[si], ax
mov cpb_intail[si], ax
sti
ret
new04 endp


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; if AL = 5, Return count in Input buffer
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
new05 proc near
mov ax, cpb_incnt[si]
ret
new05 endp

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; if AL = 6, Clear the Transmit Buffer
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
new06 proc near

cli
mov cpb_outcnt[si], NO_CHARS
mov ax, cpb_outbase[si]
mov cpb_outhead[si], ax
mov cpb_outtail[si], ax
sti
ret
new06 endp

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; if AL = 7, Return the count in the Transmit Buffer
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
new07 proc near
mov ax, cpb_outcnt[si]
ret
new07 endp

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; if AL = 8, uninstall the TSR driver, then release
;; memory
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
new08 proc near
assume ds:code

mov si, offset cpb1 ; set up for port1
cmp cpb_oint_add[si], 0 ; com port installed?
jz new0801 ; no

call unset_up ; and kill everything associated
; with this comm port

new0801:
mov si, offset cpb2 ; set up for port2
cmp cpb_oint_add[si], 0 ; com port installed?
jz new0802 ; no

call unset_up ; and kill everything associated
; with this comm port


new0802:
cli
mov dx, old_int14
mov al, 014h
push ds
mov ds, old_int14[2]
DOSINT 25h ; reset the serial port interrupt
pop ds

mov dx, orig_timer
mov al, TIMER_TICK_INT_NO
push ds
mov ds, orig_timer[2]
DOSINT 25h ; reset the timer_tick interrupt
pop ds

push cs
pop es ; free up our own memory
DOSINT 49h ; the environment
sti

ret
new08 endp

unset_up proc near
assume ds:code
mov di, cpb_base[si] ; get the base port

cli
in al, INT_MASK_PORT ; get the old flag from the 8259
mov bl, cpb_pic_mask[si] ; get the interrupt enable mask
not bl ; and turn it into a disable mask
or al, bl ; turn off the masked bit
out INT_MASK_PORT, al

lea dx, IIR[di]
xor ax, ax ; zero out for no ints
out dx, al ; and turn off interrupts

lea dx, MCR[di] ; turn off OUT2 on comm port
in al, dx
and al, not MCR_OUT2 ; hardware disable interrupts
and al, not MCR_DTR ; and drop DTR
out dx, al

mov dx, cpb_oint_add[si]
mov al, cpb_int_no[si]
push ds
mov ds, cpb_oint_add[si][2]
DOSINT 25h ; reset the comm port interrupt
pop ds

sti
ret

unset_up endp


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; NEW_TICK
;;
;; Takes over the timer tick, and simply reduces the timer count
;; every 1/18.2 of a second
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
new_tick proc far
assume ds:nothing

pushf
cmp cs:timer_tick, 0
jz no_dec1
dec cs:timer_tick

no_dec1:
cmp cs:my_timer, 0
jz no_dec2
dec cs:my_timer

no_dec2:
popf
jmp dword ptr cs:[orig_timer]
new_tick endp


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; END OF RESIDENT CODE SPACE
my_size equ (($-code)/16 + 1) ; waste a paragraph!
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Messages only needed while installing
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
say_hi db 0dh,0ah,"Installing COM driver....", 0dh,0ah,0ah,'$'
say_whoops db 0dh,0ah,"COM driver already installed!",0dh,0ah,0ah,'$'
say_bad_port1 db 0dh,0ah,"Port 1 exists and is not 3F8!",0dh,0ah,0ah,'$'
say_bad_port2 db 0dh,0ah,"Port 2 exists and is not 2F8!",0dh,0ah,0ah,'$'


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; INITIALIZE
;;
;; This routine gets called exactly once from the install routine
;; and sets up the 8250 and 8259 to generate and receive interrupts
;; It also resets and zeroes out each of the buffers
;;
;; SI should point to the correct CPB
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
initialize proc near

push ax
push dx
push di

mov di, cpb_base[si] ;the port base address (2F8, 3F8)

cli
xor ax,ax
lea dx, IER[di] ; load the interrupt register in
out dx, al ; and turn off *all* interrupts
lea dx, MCR[di] ; modem control
out dx, al ; turn off the sneaky bits

call init_buffers ; then the buffers

mov al, IER_RDA ; turn on ints for data ready
lea dx, IER[di] ; load up the interrupt register
out dx, al ; and turn 'em on

in al, INT_MASK_PORT ; get the old flag from the 8259
and al, cpb_pic_mask[si] ; turn off the masked bit
out INT_MASK_PORT, al

lea dx, MCR[di] ; put OUT2 back on
in al, dx
or al, MCR_OUT2 ; hardware enable interrupts
or al, MCR_DTR ; bring up DTR
out dx, al

lea dx, data[di] ; clean out the receive buffer
in al, dx ; a few times can't hurt
in al, dx

pop di
pop dx
pop ax

sti ;turn interrupts back on
ret ; and return

initialize endp

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; INSTALL
;;
;; First check to make sure TSRCOMM isn't already installed
;;
;;
;; For each of the comm ports, save the old interrupt vector address,
;; then remap the vector to point to the new routines, then
;; initialize the port with RX and error interrrupts turned on.
;;
;; If the comm port vector isn't what we expect, output an error
;; message and exit.
;;
;; Only play with ports which exist
;;
;; Take over the old timer_tick interrupt with the new one,
;; and take over the old serial interrupt routine
;;
;; Finally, release the pointer to the environment, and go TSR!
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

install proc near
assume ds:code

push cs ; set data segment to point to code segment
pop ds

mov ax, 0400h ; see if we exist
int 14h ; call the interrupt

cmp ax, special_return_value
jnz inst2 ; already loaded....
lea dx, say_whoops
DOSINT 9h

int 20h


inst2:
lea dx, say_hi
DOSINT 9h


push es
mov si, 040h ; the segment address for comm ports
mov es, si

cmp es:[0], PORT1 ; is it 3f8?
jnz exist1 ; no
mov ah, TRUE ; yes. set the flag
jmp chk_port_2

exist1:
cmp word ptr es:[0], FALSE ; does it exist?
jz chk_port_2
lea dx, say_bad_port1 ; something is there. No good!
jmp bomb_out

chk_port_2:
cmp es:[2], PORT2 ; is it 2f8?
jnz exist2 ; no
mov ah, TRUE ; yes. set the flag
jmp install_it

exist2:
cmp word ptr es:[2], FALSE ; does it exist?
jz install_it ; no
lea dx, say_bad_port2 ; something at 40:2 which isn't 2f8!

bomb_out:
pop es
DOSINT 9h

int 20h

install_it:
pop es

cmp ah, TRUE
jnz install_2

push ax
mov si, offset cpb1 ; set up for port1
call set_up ; set it up
pop ax

install_2:

cmp al, TRUE
jnz install_3

mov si, offset cpb2 ; set up for port2
call set_up ; set it up


install_3:
mov al, 14h ; save the old interrupt 14 services
DOSINT 35h
mov old_int14, bx
mov old_int14[2], es

lea dx, new_14 ; steal the int 14 vector
mov al, 14h
DOSINT 25h

mov al, TIMER_TICK_INT_NO ; save the "user" interrupt, not 08h
DOSINT 35h
mov orig_timer, bx
mov orig_timer[2], es

lea dx, new_tick ; steal the timer tick
mov al, TIMER_TICK_INT_NO
DOSINT 25h

mov es, env_ptr ; free up the little memory used by
DOSINT 49h ; the environment

mov dx, my_size ; so, we get to junk outselves!
DOSINT 31h

install endp

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; SET_UP
;;
;; Save and replace the approriate interrupt vectors, then call
;; initialize for the CPB pointed to by SI
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
set_up proc near

mov al, cpb_int_no[si]
DOSINT 35h
mov cpb_oint_add[si], bx
mov cpb_oint_add[si][2], es

mov al, cpb_int_no[si]
mov dx, cpb_nint_off[si]
DOSINT 25H
mov dx, 0 ; init through the BIOS first
mov al, DEF_INIT
mov ah, 0
int 14h
call initialize
ret
set_up endp

code ends
end start