;******************************************************************************
;       Communications package
;       ----------------------
;
;
;
;       Author:     Erik Franssen
;       Purpose:    Irq-handler for 8250, Interface to com.c
;       Date:       04-Sep-1990
;
; 16-Feb-1993   Accidently, it occurs that, for a Tx interrupt, the IIR
;               tells us a 'modem status' interrupt (lowest priority),
;               although the Tx hold is empty (thus in fact it must be
;               a Tx interrupt, which has higher priority).
;               We adapted the irq handler, so that, if an irq is found
;               for the 8250, we always perform following acitons:
;                   - update mstatus/lstatus
;                   - read character if available
;                   - check for Tx is hold empty and something to Tx
;******************************************************************************
                PAGE    60,132
                Title   COM Interrupt Handler

; Note: Interfacing in Large model! (far code, far ptrs)

                INCLUDE macros.inc
                @SetModel
                .CODE

;******************************************************************************
;       PUBLICS/EXTERNALS
;******************************************************************************
                public  _com_ihandler
                public  _com_addhandler
                public  _com_removehandler
                public  _com_set_ustack
                public  _com_txempty
                public  _com_enable_pic
                public  _com_disable_pic


;==========================================================================
; General equates
;==========================================================================
ARGOFFSET       equ     4 + 2*@CodeSize

ARG1            EQU     [bp+0+ARGOFFSET]    ;C-parameters locations
ARG2            EQU     [bp+2+ARGOFFSET]
ARG3            EQU     [bp+4+ARGOFFSET]
ARG4            EQU     [bp+6+ARGOFFSET]
ARG5            EQU     [bp+8+ARGOFFSET]
ARG6            EQU     [bp+10+ARGOFFSET]

;******************************************************************************
;       ASSEMBLER EQUATES
;******************************************************************************
;----------------------------------------------------------------
; COMMUNICATION Structure definition, see com.inc
; Warning: Must always be changed TOGETHER with com.inc !!!
;----------------------------------------------------------------
COM             STRUC
r_begin         dd      ?
r_end           dd      ?
r_put           dd      ?
r_get           dd      ?
t_begin         dd      ?
t_end           dd      ?
t_put           dd      ?
t_get           dd      ?
r_count         dw      ?
t_count         dw      ?
r_error         dw      ?
l_ctrl          db      ?
l_stat          db      ?
m_ctrl          db      ?
m_stat          db      ?
i8250           dw      ?
unget           dw      ?
irq             db      ?
handshake       db      ?
f_tstop         db      ?
f_tstart        db      ?
f_rstop         db      ?
f_rstart        db      ?
f_status        db      ?
t_idle          db      ?
oldhandler      dd      ?
old_ier         db      ?
old_l_ctrl      db      ?
old_m_ctrl      db      ?
old_pic_state   db      ?
old_baudrate    dw      ?
urx             dd      ?
utx             dd      ?
uptr            dw      ?
                IF      @DataSize
uptr_seg        dw      ?
                ENDIF
COM             ENDS

COM_CTS         equ     010h            ;handshake bit definitions
COM_RTS         equ     002h

COM_MAX         equ     5               ;Max. count of com-structures
INTA00          equ     020h            ;8259 port (irq 0..7)
INTA01          equ     021h
INTB00          equ     0a0h            ;2nd 8259  (irq 8..15)
INTB01          equ     0a1h
EOI             equ     020h            ;End-of-Interrupt

COM_URX_NONE    equ     0
COM_URX_PROCESS equ     1
COM_UTX_FINISHED equ    0
COM_UTX_PROCESS equ     1

;******************************************************************************
; Local variables, in cs
;******************************************************************************
                even
com_table       dd      COM_MAX dup (0) ; Ptr table to comm-structures
dos_stack_ofs   dw      0               ; offset address DOS-stack
dos_stack_seg   dw      0               ; segment address DOS-stack
ustack_ofs      dw      0               ; offset address USER-stack (0 if disabled)
ustack_seg      dw      0               ; segment address USER-stack
com_pic2_used   db      0               ; 1 means pic2 used...


;******************************************************************************
; Interrupt entry;
;******************************************************************************
_com_ihandler   proc    far                     ; interrupt handler 8250
                push    ax                      ; save registers
                push    bx
                push    cx
                push    dx
                push    si
                push    di
                push    ds
                push    es
                mov     cs:[com_pic2_used],0    ; no pic used yet
int_restart:    sub     cx,cx
int_next8250:   mov     bx,cx
                shl     bx,1
                shl     bx,1
                lds     bx,cs:[com_table + bx]  ; DS:BX points to comm struct.
                mov     ax,ds
                or      ax,bx
                je      int_next8250_1          ; If no handler, go to next

                mov     dx,[bx].i8250           ; get bas adr. rs232 port
                add     dx,2                    ; point to IIR
                in      al,dx                   ; look if from our 8250
                shr     al,1
                jnc     int82501
int_next8250_1: inc     cx                      ; check other 8250's
                cmp     cx,COM_MAX
                jb      int_next8250
                jmp     int_exit
;
; An irq for this 8250, so update mstatus/lstatus, check Rx, check Tx
; Note: dx points to IIR of 8250
;
int82501:
                add     dx, 4                   ; Get modem status
                in      al, dx
                mov     [bx].m_stat, al         ; save modem status
                dec     dx                      ; Get line status
                in      al, dx
                mov     [bx].l_stat, al         ; save line status
                test    al,00001110b            ; inc ERROR count
                je      int_lstat1              ; if framing, parity or overrun
                inc     [bx].r_error
int_lstat1:
                test    al,00001010b
                je      int_lstat2              ; (Parity error: read data)
                mov     dx, [bx].i8250          ; overrun/framing error
                in      al, dx                  ; read data, but lost
                mov     al, 07fh                ; Error detect = DEL
                jmp     short int_rx0

int_lstat2:
                test    al, 1                   ; Check if data ready
                jne     int_rx
                jmp     int_check_tx            ; Nothing, so try Tx

;
; Rx interrupt
; Woaw: a character is available in the 8250, so get and save it...
;
int_rx:         mov     dx,[bx].i8250
                in      al,dx                   ; get received character
int_rx0:
                cmp     word ptr [bx].urx,0
                jne     int_rx_uh               ; urx handler ?
                cmp     word ptr [bx].urx + 2,0
                je      int_rx1
int_rx_uh:
; Save reg's, call urx handler, and restore regs
; Woaw: ax, cx, dx and es are no longer used from here on, so who matters...
                xor     ah,ah                 ; ax now contains rx character
                push    bx                    ;used registers
                push    ds
; Switch to a local stack
                mov     cs:dos_stack_seg,ss   ;save stack segment
                mov     cs:dos_stack_ofs,sp   ;save stack pointer
                cmp     cs:ustack_ofs, 0
                je      int_rx_uh1
                mov     ss, cs:ustack_seg     ;move local stack to ss:sp
                mov     sp, cs:ustack_ofs
int_rx_uh1:                
; Push urx function arguments                
                IF      @DataSize
                push    [bx].uptr_seg
                ENDIF
                push    [bx].uptr             ; void *uptr
                push    ax                    ; rx character
; Set ds                 
                push    ds
                pop     es                    ;Set es to com seg
                mov     cx,DGROUP
                mov     ds,cx                 ;Set ds to C-ds
                
                call    es:[bx].urx           ;(*urx)(uchar c,void *uptr)
                IF      @DataSize
                add     sp,6                  ;Clean up stack (C-arguments)
                ELSE
                add     sp,4                  ;Restore registers
                ENDIF
; Restore original DOS stack
                mov     ss, cs:dos_stack_seg  ;restore original DOS-stack
                mov     sp, cs:dos_stack_ofs
;
                pop     ds
                pop     bx
                cmp     ah,COM_URX_PROCESS
                je      int_rx1               ;Save al in Rx-buffer
                jmp     int_rx_throw          ;else throw it away
;
int_rx1:        les     di,[bx].r_put
; Check if buffer overflow. If buffer full, throw away character
                mov     cx,word ptr [bx].r_end
                sub     cx,word ptr [bx].r_begin
                cmp     cx,word ptr [bx].r_count
                jle     int_rx_throw
                mov     es:[di],al
                inc     di
                cmp     di,word ptr [bx].r_end
                jne     short int_rx3
                mov     di,word ptr [bx].r_begin
int_rx3:        mov     word ptr [bx].r_put,di
                inc     [bx].r_count
int_rx_throw:
;
; handle CTS/RTS 
; set RTS off : a/ _comm_handshake ON (1)
;               b/ RTS on
;               c/ _comm_r_count > 87.5 % of buffersize

                test    [bx].handshake,COM_RTS
                je      int_rx2                 ; no RTS handling - try soft
                mov     dx,[bx].r_count
                mov     al,[bx].m_ctrl          ; get Ctrl bit
                test    al,00000010b            ; RTS test
                je      int_rx2                 ; OFF
                call    calc_flowsize           ; Get di = 87.5 % of buffersize
                cmp     dx,di                   ; RTS is ON :receive count > di
                jbe     int_rx2
                xor     al,00000010b
                mov     dx,[bx].i8250
                add     dx,4
                out     dx,al                   ; new rts level = off
                mov     [bx].m_ctrl,al

; Handle soft protocol : only if _comm_f_stat.b7 SET
; Send _comm_f_rstop  : if _comm_f_rstop != XNUL
;                          _comm_r_count > 1888
;                          _comm_f_status.b0 = 0

int_rx2:
                mov     al,[bx].f_status
                test    al,080h         
                je      int_check_tx            ; no soft handling
                test    al,1
                jne     int_check_tx            ; XOFF already sent...
                mov     dx,[bx].r_count
                call    calc_flowsize           ; Get di = 87.5 % of buffersize
                cmp     dx,di                   ; RTS is ON :receive count > di
                jbe     int_check_tx
                mov     cl,[bx].f_rstop
                or      cl,cl
                je      int_check_tx            ; if NULL no handshake
                or      al,1
                mov     [bx].f_status,al        ; save new value
                mov     al,cl                   ; al -> transmit buffer
                call    put_character

;
; Check if Tx ready
;
int_check_tx:
;/////////////////////////////////////////////////////////////////
; In case the 'urx' handler called com_start_tx() (thus during
; this interrupt, a new char. is sent), then the l_stat byte
; no longer reflects the current state. Since a char is just sent
; to the 8250, the THRE bit is no longer set. 'l_stat', however,
; still reflects the status at the begin of our irq routine, thus
; THRE set (since no Tx was busy).
; Therefore, we will always use the i8250 line status to check...
; (CDMP does this: slave starts Tx during last Rx ch. decoding)
;/////////////////////////////////////////////////////////////////
;               mov     al, [bx].l_stat         ; Get line status
                mov     dx, [bx].i8250
                add     dx, 5                   ; Get line status
                in      al, dx
;
                test    al, 020h
                je      jmp_int_tx3
;
; Tx ready, so check if something to tx
; Tx interrupt
;
int_tx:
                cmp     word ptr [bx].utx,0
                jne     int_tx_uh               ; utx handler ?
                cmp     word ptr [bx].utx + 2,0
                je      int_tx1
int_tx_uh:
; !!! We always check for tx. However, we must only call the utx handler
;     if this handler has started tx irq (via com_start_tx())
                test    [bx].t_idle, 0ffh       ; If Tx idle, don't call utx
                jne     int_tx3                 ; so Tx completely done...
; Save reg's, call utx handler, and restore regs
; Woaw: ax, cx and es are no longer used from here on, so who cares...
                push    bx
                push    dx
                push    ds
; Switch to a local stack
                mov     cs:dos_stack_seg,ss   ;save stack segment
                mov     cs:dos_stack_ofs,sp   ;save stack pointer
                cmp     cs:ustack_ofs, 0
                je      int_tx_uh1
                mov ss, cs:ustack_seg         ;move local stack to ss:sp
                mov sp, cs:ustack_ofs
int_tx_uh1:
; Push utx function arguments
                IF      @DataSize
                push    [bx].uptr_seg
                ENDIF
                push    [bx].uptr             ; void *uptr
; Set ds
                push    ds
                pop     es                    ;Set es to com seg
                mov     cx,DGROUP
                mov     ds,cx                 ;Set ds to C-ds
                call    es:[bx].utx           ;(* utx)(void *uptr)
                IF      @DataSize
                add     sp,4                  ;Clean up stack (C-arguments)
                ELSE
                add     sp,2
                ENDIF
; Restore original DOS stack 
                mov     ss, cs:dos_stack_seg  ;restore original DOS-stack
                mov     sp, cs:dos_stack_ofs
;
                pop     ds
                pop     dx
                pop     bx
                cmp     ah,COM_UTX_FINISHED
                je      int_tx_kill           ;Stop Tx irq if nothing more
                mov     dx,[bx].i8250         ;
                out     dx,al                 ;Else Tx character
jmp_int_tx3:    jmp     short int_tx3         ;Tx character in al

int_tx1:
                cmp     [bx].t_count,0
                je      int_tx_kill             ; tcount = 0, no more int
                test    [bx].handshake,COM_CTS
                je      int_tx2
                test    [bx].m_stat,010h        ; Test CTS
                je      int_tx_kill             ; No CTS = kill int.
int_tx2:
; Warning: May be the Tx irq was stopped because of CTS drop and
; hardware handshaking enabled. Therefore, if idle, re-start Tx irq
                call    tx_character
                test    [bx].t_idle, 0ffh       ; Interrupt still busy?
                je      int_tx3
                mov     dx, [bx].i8250          ; No Tx-irq, so restart it
                inc     dx                      ; Point to IER
                in      al, dx
                or      al, 2                   ; Tx Hold Reg. Empty int. enable
                out     dx, al
                mov     [bx].t_idle, 0
int_tx3:        jmp     short int_done

int_tx_kill:    mov     [bx].t_idle,1
                mov     dx,[bx].i8250
                inc     dx                      ; Point to IER
                in      al,dx
                and     al,0fdh                 ; Mask THRE int.
                out     dx,al
                dec     dx

int_done:
; Remember if pic2 used (irq >= 8)
                cmp     [bx].irq, 8
                jb      int_done1
                mov     cs:[com_pic2_used], 1
int_done1:
                jmp     int_restart             ; DOUBLE INTERRUPTS ???

int_exit:
; Because of int_restart, several irq numbers can be served in int8250().
; Therefore, don't use "mov dl,[bx].irq" to check which pic used,
; but use 'new' variable com_pic2_used
                mov     dl,cs:[com_pic2_used]
                call    non_specific_eoi        ; non specific EOI
                pop     es
                pop     ds
                pop     di
                pop     si
                pop     dx
                pop     cx
                pop     bx
                pop     ax                      ; recall registers
                iret


tx_character    proc    near
                dec     [bx].t_count            ; tcount -= 1
                les     di,[bx].t_get
                mov     al,es:[di]
                mov     dx,[bx].i8250           ; Get character
                out     dx,al                   ; and Tx it
                inc     word ptr [bx].t_get
                mov     ax,word ptr [bx].t_end
                cmp     word ptr [bx].t_get,ax  ; Update pointers
                jne     short tx_character1
                mov     ax,word ptr [bx].t_begin
                mov     word ptr [bx].t_get,ax
tx_character1:  ret
tx_character    endp

put_character   proc    near
                push    ax
                les     di,[bx].t_put
                mov     es:[di],al
                inc     word ptr [bx].t_put
                mov     ax,word ptr [bx].t_end
                cmp     word ptr [bx].t_put,ax
                jne     put_character1
                mov     ax,word ptr [bx].t_begin
                mov     word ptr [bx].t_put,ax
put_character1: inc     [bx].t_count
                pop     ax
                ret
put_character   endp

; Calculate size of buffer that may be reached before flow control starts
; Return:   di = 87.5 % of buffersize
calc_flowsize   proc    near
                push    ax
                mov     di,word ptr [bx].r_end
                sub     di,word ptr [bx].r_begin    ;Calc. buffersize
                mov     ax,di
                shr     ax,1
                shr     ax,1                    ; Flow size = bufsize / 8
                shr     ax,1                    ; (12.5 % from buffer free)
                sub     di,ax                   ; size - 12.5% = 87.5%
                pop     ax
                ret
calc_flowsize   endp

_com_ihandler   endp

;******************************************************************************
; Functions, called from com.c
;******************************************************************************

;------------------------------------------------------------------
; Add com structure to com_table. This means that given com-port
; will also be handled with the one and only com_ihandler()
; Parameters:   ARG1        COM far *com
; Return:       1           ok
;               0           error (e.g. com_table full)
;------------------------------------------------------------------
_com_addhandler proc
                push    bp
                mov     bp,sp
                sub     cx,cx                   ;Start searching at begin
com_add01:      mov     bx,cx
                shl     bx,1
                shl     bx,1
                                                ;Get address of com-structures
                mov     ax,word ptr cs:[com_table+bx]
                mov     dx,word ptr cs:[com_table+bx+2]
                or      ax,dx                   ;Check if null pointer
                jne     com_add10
                                                ;If so, replace by argument
                mov     ax,ARG1
                mov     word ptr cs:[com_table+bx],ax
                mov     ax,ARG2
                mov     word ptr cs:[com_table+bx+2],ax
                mov     ax,1
                jmp     short com_add90
com_add10:      inc     cx
                cmp     cx,COM_MAX
                jb      com_add01
                sub     ax,ax                   ;Indicate com_table full
com_add90:      pop     bp
                ret
_com_addhandler endp

;------------------------------------------------------------------
; Remove com structure from com_table.
; Parameters:   ARG1        COM far *com
; Return:       1           ok
;               0           error (not found in com_table)
;------------------------------------------------------------------
_com_removehandler proc
                push    bp
                mov     bp,sp
                sub     cx,cx                   ;Start searching at begin
com_remove01:   mov     bx,cx
                shl     bx,1
                shl     bx,1
                                                ;Get address of com-structures
                mov     ax,word ptr cs:[com_table+bx]
                mov     dx,word ptr cs:[com_table+bx+2]
                cmp     ax,ARG1                 ;And compare with arg.
                jne     com_remove10
                cmp     dx,ARG2
                jne     com_remove10
                                                ;Equal, so free it now...
                mov     word ptr cs:[com_table+bx],0
                mov     word ptr cs:[com_table+bx+2],0
                mov     ax,1
                jmp     short com_remove90
com_remove10:   inc     cx
                cmp     cx,COM_MAX
                jb      com_remove01
                sub     ax,ax                   ;Indicate not found in table
com_remove90:   pop     bp
                ret
_com_removehandler  endp

;------------------------------------------------------------------
; Instruct the COM to switch to a local stack when calling
; an URX and/or UTX handler from within interrupt.
;
; extern void com_set_ustack(char *ustack, int ustack_size);
;
; Parameters:   ARG1        char _far *ustack: ptr to the buffer
;               ARG3        uint ustack_size:  size of buffer
;
; use ustack_size == 0 to remove the local stack
;
; !!! WARNING: size must be big enough, e.g. 512 bytes
;              (depends on stack usage by u?x handlers)
;
; Return:       none
;
;------------------------------------------------------------------
_com_set_ustack proc
                push    bp
                mov     bp,sp

                cli
                cmp     word ptr ARG3, 64
                jae     com_set_u1
                mov     cs:ustack_ofs, 0    ; Disable local stack usage
                jmp     short com_set_u90
com_set_u1:
                mov     ax,ARG2         ; ustack segment
                mov     cs:ustack_seg, ax
                mov     ax, ARG1        ; ustack offset (begin of buffer)
; We will set the real stack ptr to begin of buffer + buffer_size - 2
; (stack ptr decrements)
                add     ax, ARG3
                sub     ax, 2
                mov     cs:ustack_ofs, ax
com_set_u90:
                sti
                
                pop     bp
                ret
_com_set_ustack endp

;-----------------------------------------------------------------------------
; Waits tx empty (function from GVR)
; called before re-programming baudrate and/or format
; Parameters:   ARG1    address i8250
; Return:       none
;-----------------------------------------------------------------------------
_com_txempty    proc
                push    bp
                mov     bp,sp
                mov     dx,ARG1         ;Get address i8250
                ;+ a short delay - time-out
                push    cx
                mov     cx,0ffffh       ; wait a very long time (only if error)
                add     dx,5
txempty2:       in      al,dx
                and     al,01100000b    ; wait both bits set !          
                xor     al,01100000b            
                je      txempty1        ; OK -wait a little -
                loop    txempty2
                jmp     short txemptye  ; AL <> 0
txempty1:       mov     cx,128
                loop    $               ; wait...
txemptye:       pop     cx
                sub     dx,5
                or      al,al           ; set flaggy .eq.=OK
                pop     bp
                ret
_com_txempty    endp

;-----------------------------------------------------------------------------
; Enable Interrupt controller
;
; Parameters:   ARG1    uint irq        Interrupt number [0..15]
; Return:       1 if irq was set, 0 if was not set
;-----------------------------------------------------------------------------
_com_enable_pic proc
                push    bp
                mov     bp,sp
                mov     dx,INTA01       ;suppose irq 0..7
                mov     cx,ARG1         ;mask pic
                cmp     cl,8            ;irq > 8 goes to second 8259
                jb      enable_01
                sub     cl,8
                mov     dx,INTB01
enable_01:      cli                     ;disable IRQ
                in      al,dx           ;get mask of PIC
                jmp     short $+2       ;delayke (Techn. Reference)
                mov     ah,00000001b    ;assume Irq 0
                rol     ah,cl           ;rotate number of times
                sub     bx,bx           ;Suppose irq not programmed
                test    al,ah
                jne     enable_05
                inc     bx              ;Return 1 if irq was set (thus bit=0)
enable_05:
                not     ah              ;reverse all bits
                and     al,ah           ;enable IRx
                out     dx,al           ;send back to PIC
                cmp     word ptr ARG1,8
                jb      enable_10
                in      al,INTA01       ;Ensure Master Level 2 enabled
                and     al,0fbh         ;2nd pic in cascade with 1st pic,
                out     INTA01,al       ;through irq 2
enable_10:      sti
                mov     ax,bx           ;Set return value
                pop     bp
                ret
_com_enable_pic endp

;-----------------------------------------------------------------------------
; Disable Interrupt controller
;
; Parameters:   ARG1    uint irq        Interrupt number [0..15]
; Return:       None
;-----------------------------------------------------------------------------
_com_disable_pic    proc
                push    bp
                mov     bp,sp
                mov     dx,INTA01       ;suppose irq 0..7
                mov     cx,ARG1         ;mask pic
                cmp     cl,8            ;irq > 8 goes to second 8259
                jb      disable_01
                sub     cl,8
                mov     dx,INTB01
disable_01:     cli                     ;disable IRQ
                in      al,dx           ;get mask of PIC
                jmp     short $+2       ;delayke (Techn. Reference)
                mov     ah,00000001b    ;assume Irq 0
                rol     ah,cl           ;rotate number of times
                or      al,ah           ;enable IRx
                out     dx,al           ;send back to PIC
                sti
                pop     bp
                ret
_com_disable_pic    endp

;------------------------------------------------------------------------------
; Generate non specific eoi to pic, to enable interrupt again
;
; Parameters:   dl      1 means pic2 used
; Return:       None
;------------------------------------------------------------------------------
non_specific_eoi    proc    near
                mov     al,EOI          ;End of interrupt: non specific EOI
                or      dl,dl           ;(Allow irq again from that source)
                jz      eoi_01
                out     INTB00,al       ;to 2nd 8259 if irq 8..15
eoi_01:         out     INTA00,al       ;and always to 1st 8259
                ret
non_specific_eoi    endp


                END
