;       SCCSID = @(#)atsub.asm  6.5 91/11/14
; ***************************************************************************
; *
; *                       IBM/Microsoft Confidential
; *
; *                 Copyright (c) IBM Corporation  1987, 1990
; *                 Copyright (c) Microsoft Corp.  1987, 1990
; *                           All Rights Reserved
; *
; ***************************************************************************

        PAGE    80,132
        .286p

        TITLE   com01.sys - Asynchronous Communication Device Driver
        NAME com01

;       Bryan Diehl
;       David Gilman

;***    atsub.asm
;
;       This file contains the support procedures for the AT com
;       driver.

;       Modification History
;
;       DJG     01/06/87        Re-written to conform to MS standard for
;                               style, clarity and efficiency.
;       JGT     05/10/88        Add enhanced baud rate support (p587-cpd12)
;       JGT     06/20/88        Fix bad jump bug in ComputeHHS (p1037-cpd12)
;       JGT     06/24/88        Fix inc bug in ComputeWTO (p1160 - cpd12)
;       YN      05/25/89        MVDM Support - @VDM
;       ACW     04/16/91        @PVW Added perfview counters/timers
;       JAG     07/29/93        @71491 Fix ComputeHHS for RTS=TOG
;       RAC     12/7/93         Make Perfview conditional code
;       RDW     03/30/94        81245 SMC SIO chip cause hang enabling FIFO UART
;       JAG     01/06/94        @76898 Fix RTS=TOG; need to mask before cmp
;   ChageTeam   09/28/95        135047 ComputeAPO for FIFO=ON should use flags3 to
;                               set trigger level.  Not (FF_CLEAR_RX or FF_CLEAR_TX)
;       JAG     10/31/96        169017 - multiple small DosWrites hang system
;



include devhlp.inc
include devsym.inc
include basemaca.inc
;include osmaca.inc
include realmac.inc
include error.inc
include filemode.inc
include atcom.inc
include ateisa.inc
include iodelay.inc
.list
        EXTRN   ClkIntrvl:WORD          ; system clock interval
        EXTRN   Com1:WORD               ; data for Com1
        EXTRN   Com2:WORD               ; data for Com2
        EXTRN   Com3:WORD               ; data for Com3
        EXTRN   Com4:WORD               ; data for Com4
        EXTRN   DevHlp:DWORD            ; device help entry point
        EXTRN   Flags:BYTE
        EXTRN   ShrdIRQ1:BYTE
        EXTRN   Ticker:NEAR
        extrn   CmxGenFail:near
        extrn   CmxInterrupted:near
        extrn   ComRFlushSub:near
        extrn   ComWFlushSub:near
        extrn   WriteQueue:near
        extrn   cinterr:near


DSEG    SEGMENT

; DataSegSig is a hack so that we can identify our DATA segment.

DataSegSig      DW      SIGNATURE

; Ready is a list of request packets which are ready to be run.

        PUBLIC  Ready
Ready           RP_List <>

; MaskTab contains masks for the four character sizes that are supported.

MaskTab         DB      CHAR_5_MASK
                DB      CHAR_6_MASK
                DB      CHAR_7_MASK
                DB      CHAR_8_MASK

; 16550A HW FIFO RX trigger level encodings
RxTrigTbl       DB      1,4,8,14

DSEG    ENDS

CSEG    SEGMENT
        ASSUME  cs:CSEG

ALIGN 4


;**     CheckTX - enable/disable the TX interrupt as appropriate
;
;       CheckTX determines if the transmitter should be enabled
;       or disabled. This determination is based upon the
;       current state of the driver and the modem control signals.
;
;       The transmitter will be enabled if:
;
;               - BREAK is NOT set AND
;                 our output HW handshake lines are high AND
;                 XON, XOFF or TxImmed is pending
;
;               - BREAK is NOT set AND
;                 our output HW handshake lines are high AND
;                 XOFF has not been received AND
;                 (full duplex set OR XOFF has not been sent) AND
;                 user data is ready to be sent
;
;       Otherwise, the transmitter will be disabled
;
;       ENTRY   (ds:si) -> ComInfo
;               Interrupts disabled
;
;       EXIT    if 'C' set
;                       TX interrupt disabled
;               else
;                       TX interrupt enabled
;               Interrupts disabled
;
;       USES    ax dx

Procedure CheckTX,NEAR
        ASSUME cs:CSEG,ds:DSEG,es:NOTHING,ss:NOTHING

        ChkComInfoPtr
        push    ax
        mov     dx,[si].ci_port
        add     dx,R_INTEN              ; (dx) -> interrupt enable reg

                                        ;@VDM start new
                                        ;@VDM if in use by a VDM
        test    [si].ci_vdm_flag,VDM_Flag_InUse
        jz      stx009                  ;@VDM
                                        ;@VDM
        mov     al,[si].ci_msrshadow    ; (al) = msrshadow
        and     al,[si].ci_outhhslines  ; mask bits of interest
        cmp     al,[si].ci_outhhslines
        jne     stx003                  ; modem lines down

        cmp     [si].ci_vdm_rx_state,1  ;@VDM are we in state 1?
        jne     stx003                  ;@VDM no, go enable
                                        ;@VDM
        cmp     [si].ci_qout.ioq_count,0;@VDM yes, check if data is in queue
        jne     stx_on                  ;@VDM yes, go enable
                                        ;@VDM
stx003:                                 ;@VDM DISABLE
        jmp     stx_off                 ;@VDM go do disable
                                        ;@VDM
stx009:                                 ;@VDM end new
        call    CheckRTSToggle

        mov     dx,[si].ci_port
        add     dx,R_INTEN              ; (dx) -> interrupt enable reg
        mov     ah,[si].ci_hsflag       ; (ah) =  ci_hsflag

        test    ah,HS_BREAK_SET
        jnz     stx_off                 ; break being sent

        mov     al,[si].ci_msrshadow    ; (al) = msrshadow
        and     al,[si].ci_outhhslines  ; mask bits of interest
        cmp     al,[si].ci_outhhslines
        jne     stx_off                 ; modem lines down

        test    ah,HS_XON_PENDING OR HS_XOFF_PENDING OR HS_TX_IMMED
        jnz     stx_on                  ; XON, XOFF or TX_IMMED pending

        cmp     [si].ci_qout.ioq_count,0
        jz      stx_off                 ; no data enqueued

stx10:  test    ah,HS_XOFF_RECEIVED
        jnz     stx_off                 ; XOFF received, can't tx

        test    [si].ci_dcb_flags2,F2_FULL_DUP
        jnz     stx_on                  ; full dup, can tx even if XOFF sent

        test    ah,HS_XOFF_SENT
        jnz     stx_off                 ; XOFF sent and NOT full dup, can't tx

stx_on: ; enable transmit interrupts

        min     al,dx                   ; (al) = IER

        test    al,IE_TX
        jnz     stxx                    ; TX interrupt already on

        or      al,IE_TX                ; enable TX interrupts (clears carry)
        out     dx,al                   ; could cause interrupt to be pending!
        push ax
        DevIODelay ax
        pop  ax
        out     dx,al                   ; write twice for 8250 bug
        jmp     SHORT stxx

stx_off:        ; disable transmit interrupts
        min     al,dx                   ; (al) = IER
        and     al,NOT IE_TX            ; disable TX interrupts
        mout    dx,al
        stc

stxx:   pop     ax
        ret

EndProc CheckTX

;**     CheckRTSToggle - raise/lower RTS for RTS toggling
;
;       CheckRTSToggle determines if RTS should be raised of lowered.
;       This determination is based upon the current state of the
;       driver and the modem control signals.
;
;       RTS will be controlled if RTS toggling is enabled as follows:
;
;           RTS will be raised if:
;               - BREAK is set OR
;               - XON, XOFF or TxImmed is pending OR
;               - user data is ready to be sent AND XOFF not sent or received
;
;           Otherwise, RTS drop pending will be marked (processed in ticker)
;
;       ENTRY   (ds:si) -> ComInfo
;
;       EXIT
;
;       USES    al dx

Procedure CheckRTSToggle,NEAR
        ASSUME cs:CSEG,ds:DSEG,es:NOTHING,ss:NOTHING

        ChkComInfoPtr

        ; check for RTS toggling
        mov     al,[si].ci_dcb_flags2
        and     al,F2_RTS_MASK
        cmp     al,F2_RTS_TOGGLE
        jne     crtx                    ; RTS toggling not enabled

        mov     dh,[si].ci_hsflag       ; (dh) =  ci_hsflag

        test    dh,HS_BREAK_SET
        jnz     crt_on                  ; break being sent

        test    dh,HS_XON_PENDING OR HS_XOFF_PENDING OR HS_TX_IMMED
        jnz     crt_on                  ; XON, XOFF or TX_IMMED pending

        cmp     [si].ci_qout.ioq_count,0
        je      crt_off                 ; no current write request

        test    dh,HS_XOFF_RECEIVED
        jnz     crt_off                 ; XOFF received, can't tx

        test    [si].ci_dcb_flags2,F2_FULL_DUP
        jnz     crt_on                  ; full dup, can tx even if XOFF sent

        test    dh,HS_XOFF_SENT
        jnz      crt_off                 ; XOFF sent and NOT full dup, can't tx

crt_on: ; raise RTS
        and     [si].ci_flagx,NOT FX_RTS_DROP_PENDING   ; clear flag

        mov     dx,[si].ci_port
        add     dx,R_MODMC              ; (dx) -> modem control reg
        min     al,dx                   ; (al) = MCR
        or      al,MC_RTS               ; turn RTS on
        mout    dx,al
        jmp     SHORT crtx

crt_off: ; drop RTS
        mov     dx,[si].ci_port
        add     dx,R_MODMC              ; (dx) -> modem control reg
        min     al,dx                   ; (al) = MCR
        test    al,MC_RTS
        jz      crtx                    ; RTS already down

        or      [si].ci_flagx,FX_RTS_DROP_PENDING
                ; drop RTS at timer tick (ticker) time when
                ; THR and TSR are empty
crtx:   ret

EndProc CheckRTSToggle


;**     ComputeAPO - compute automatic priority override
;
;       Check cominfo flags and compute the FIFO HW mode (on or off),
;       the Recieve Trigger Level and the Transmit Buffer Load Count.
;
;       ENTRY   (ds:si) -> ComInfo
;
;       EXIT    cominfo fields and hardware set
;
;       USES    ax dx
;
;
;       if (FIFO == off) {
;           RX_trig = 1;
;           TX_cnt  = 1;
;       }
;
;       if (FIFO == on) {
;           RX_trig = user_RX_trig;
;           TX_cnt  = user_TX_cnt;
;       }
;
;       if (FIFO == apo) {
;           if (out_XO || inDSR)
;               RXtrig = 1
;           else
;               RXtrig = 8
;
;           if (outCTS || outDSR || outDCD || out_XO)
;               TXcnt = 1
;           else
;               TXcnt = 16
;       }

; Declare as hybrid so that ESP-specific can call as FAR,
; and non-ESP code can call as NEAR.
Procedure ComputeAPO,HYBRID
        ASSUME cs:CSEG,ds:DSEG,es:NOTHING,ss:NOTHING

        ChkComInfoPtr

        mov     dx,[si].ci_port
        add     dx,R_FIFOC              ; dx -> FIFO control register

        .errnz  F3_FIFO_MASK - 00011000b
        .errnz  F3_FIFO_NA   - 00000000b
        .errnz  F3_FIFO_OFF  - 00001000b
        .errnz  F3_FIFO_ON   - 00010000b
        .errnz  F3_FIFO_APO  - 00011000b

        mov     al,[si].ci_dcb_flags3   ; al = flags3
        mov     ah,al                   ; ah = flags3

        and     ah,F3_FIFO_MASK         ; ah = flags3 fifo mode flags only
        jz      capox                   ; FIFO not available, done

        cmp     ah,F3_FIFO_OFF
        je      fifo_off                ; FIFO = OFF, turn fifo HW off

        cmp     ah,F3_FIFO_ON
        je      fifo_on                 ; FIFO = ON,  use user values

        ; FIFO = APO
        ; compute RX trigger level
        .errnz  F3_RX_MASK - 01100000b
        .errnz  F3_RX_1    - 00000000b
        and     al,NOT (F3_RX_MASK OR F3_TX_16)         ; RX trig = 1
                                                        ; TX cnt  = 1
        test    [si].ci_dcb_flags1,F1_IN_DSR_SENSE
        jnz     capo10                                  ; IN_DSR, RX trig = 1
        test    [si].ci_dcb_flags2,F2_OUT_XO
        jnz     capo10                                  ; OUT_XO, RX trig = 1

        or      al,F3_RX_8                              ; RX trig = 8

capo10: ; compute TX count
        test    [si].ci_dcb_flags1,F1_OUT_FLOW          ; out CTS, DSR or DCD
        jnz     capo20                                  ; OUT_FLOW, TX cnt = 1
        test    [si].ci_dcb_flags2,F2_OUT_XO
        jnz     capo20                                  ; OUT_XO, TX cnt = 1

        or      al,F3_TX_16                             ; TX cnt = 16

capo20: mov     [si].ci_dcb_flags3,al                   ; save flags3 value
        ; FALL THROUGH!

fifo_on:        ; turn FIFO HW on, set rx trig
        .errnz  FF_RX_MASK - 11000000b
        .errnz  F3_RX_MASK - 01100000b

; 81245 The following code has been added (or modified) to handle defect SMC
;       UARTs. This code was added per recommendation of SMC Engineering.

; 81245 Check for 16550A (FIFO)
        mov     al, FF_CLEAR_RX OR FF_CLEAR_TX  ; 81245
        mout    dx, al                  ; 81245 try to turn off 16550A FIFOs

        add     dx, R_MODMC-R_FIFOC     ; 81245 (dx) -> MCR
        min     al, dx                  ; 81245 Get current value of MCR
        or      al, MC_LOOP             ; 81245 Set loopback ON to clear THR

        mout    dx, al                  ; 81245
        add     dx, R_DATA-R_MODMC      ; 81245 (dx) -> RBR
        min     al, dx                  ; 81245 Clear the 1 byte RBR

; 81245 At this point, we don't have to worrt about clearing the loop back
;       in the MCR as we will do this later when we clear the MCR.

        add     dx, R_FIFOC             ; 81245 (dx) -> FCR
;135047        mov     al, FF_CLEAR_RX OR FF_CLEAR_TX ; 81245 restore (al)
        mov     al, [si].ci_dcb_flags3  ; 135047 restore (al) to FLAGS3

        and     al,F3_RX_MASK           ; al = RX trigger level from flags3
        shl     al,1                    ; al = RX trigger level bits for FIFO
        or      al,FF_ENABLE
        mout    dx,al                   ; send data to FIFO control reg

        add     dx, R_MODMC-R_FIFOC     ; 81245 (dx) -> MCR
        min     al, dx                  ; 81245 Get current MCR
        and     al, NOT MC_LOOP         ; 81245 Clear the LOOP back bit
        mout    dx, al                  ; 81245
        jmp     short capox

fifo_off:       ; turn FIFO HW off
        and     [si].ci_dcb_flags3,NOT (F3_RX_MASK OR F3_TX_16)
        xor     al,al                   ; al = 0 (disable FIFO)
        mout    dx,al                   ; send data to FIFO control reg

capox:  ret

EndProc ComputeAPO


;**     ProcBlockNI - block the current thread non-interruptably
;**     ProcBlock   - block the current thread interruptably
;
;       ProcBlock blocks the calling thread using the
;       virtual address of its request packet as the key.
;       Callers specify the appropriate timeout.
;
;       ENTRY   (es:di) -> tiled request packet
;               dx:cx = time out value in milliseconds
;               Interrupts disabled (usually)
;
;       EXIT    'C' clear ProcRun caused wakeup
;               'C' set
;                       'Z' flag clear if interrupted
;                       'Z' set if timed out
;               Interrupts enabled
;
;       USES    ax bx

Procedure ProcBlockNI,NEAR
        ASSUME cs:CSEG,ds:DSEG,es:NOTHING,ss:NOTHING

        mov     bx,(0100h OR DevHlp_ProcBlock)  ; bx = ProcBlock non-interrupt
        jmp     SHORT pb10

Entry   ProcBlock,,,nocheck

        mov     bx,(0000h OR DevHlp_ProcBlock)  ; bx = ProcBlock interrupt

pb10:   ChkRPPtr

        mov     ax,es
        xchg    bx,di                   ; ax:bx => request packet
                                        ; di = ProcBlock + flag

        xchg    di,dx                   ; di:cx = time out
                                        ; dx = ProcBlock + flag
        DevHelp                         ; block (enables interrupts)

        xchg    di,dx                   ; dx:cx = time out
        xchg    bx,di                   ; (es:di) -> RP

        ChkRPPtr

        pushf                   ; save DevHlp_ProcBlock return codes
        test    Flags,F_SHUT_DOWN
        jz      pbx             ; driver not shut down, OK

        jmp     CmxGenFail      ; driver is shut down, fail all requests

pbx:    popf                    ; restore DevHlp_ProcBlock return codes
        ret

EndProc ProcBlockNI


;**     ProcRun - run the current thread
;
;       ProcRun runs the thread that owns the request packet pointed
;       to by the virtual address in (es:di).
;       ProcRun also sets the STDON bit in the request packet status field.
;
;       ENTRY   (es:di) -> tiled request packet
;
;       EXIT    NONE
;
;       USES    ax bx dx

; Declare as hybrid so that ESP-specific can call as FAR,
; and non-ESP code can call as NEAR.
Procedure ProcRun,HYBRID
        ASSUME cs:CSEG,ds:DSEG,es:NOTHING,ss:NOTHING

        ChkRPPtr

        or      es:[di].PktStatus,STDON         ; set STDON bit

        mov     ax,es
        mov     bx,di                   ; ax:bx => request packet

        mov     dl,DevHlp_ProcRun
        DevHelp                         ; run (enables interrupts)

        ret

EndProc ProcRun


;**     DisableRemoteTX - disable the remote transmissions

;       DisableRemoteTX disables the remote transmitter using whatever
;       handshaking mode(s) are currently active. That is, it will disable
;       the hardware handshake lines and/or send an XOFF character.
;
;       ENTRY   (ds:si) -> ComInfo
;
;       EXIT    interrupts enabled
;
;       USES    ax dx

Procedure DisableRemoteTX,NEAR
        ASSUME cs:CSEG,ds:DSEG,es:NOTHING,ss:NOTHING

        ChkComInfoPtr

        mov     ah,[si].ci_hhslines     ; (ah) = hardware handshake lines

        cli

        or      ah,ah                   ; check for any input HS lines up
        jz      drt4                    ; no handshake lines to disable

        call    DisableRemoteTXHS       ;cj go lower HS lines

drt4:   test    [si].ci_dcb_flags2,F2_IN_XO     ; input flow enabled?
        jz      drtx                            ; input XON/XOFF disabled

        call    DisableRemoteTXXO       ;cj go send XOFF

        call    CheckTX                 ; attempt to enable TXer

drtx:   sti

        ret

EndProc DisableRemoteTX

;********************** START OF SPECIFICATIONS *********************
;*
;*  SUBROUTINE NAME:    DisableRemoteTXHS
;*
;*  DESCRIPTIVE NAME:   Lower input handshaking lines
;*
;*  FUNCTION:   Set Modem Control to disable input HS lines.
;*
;*  ENTRY POINT:  DisableRemoteTXHS
;*
;*  LINKAGE:    Near
;*
;*  USES:       AX
;*
;*  INPUT:      DS:SI -> ComInfo data area
;*
;*  OUTPUT:     none
;*
;*  EXIT-NORMAL: none
;*
;*  EXIT_ERROR:  none
;*
;*  EFFECTS:   ComInfo data area
;*
;*  INTERNAL REFERENCES:
;*      None
;*
;*  EXTERNAL REFERENCES: none
;*
;*********************** END OF SPECIFICATIONS **********************

Procedure DisableRemoteTXHS,HYBRID
        ASSUME cs:CSEG,ds:DSEG,es:NOTHING,ss:NOTHING

        mov     ah,[si].ci_hhslines     ;cj (ah) = hardware handshake lines
        mov     dx,[si].ci_port         ;cj
        add     dx,R_MODMC              ;cj (dx) -> modem control reg.
        min     al,dx                   ;cj (al) = current MCR values
        not     ah                      ;cj (ah) = hardware lines to disable
        and     al,ah                   ;cj turn off hardware handshake lines
        mout    dx,al                   ;cj

        ret                             ;cj

EndProc DisableRemoteTXHS

;********************** START OF SPECIFICATIONS *********************
;*
;*  SUBROUTINE NAME:    DisableRemoteTXXO
;*
;*  DESCRIPTIVE NAME:   Send XOFF
;*
;*  FUNCTION:   Sends XOFF if not previously sent
;*
;*  ENTRY POINT:  DisableRemoteTXXO
;*
;*  LINKAGE:    Near
;*
;*  USES:       AX
;*
;*  INPUT:      DS:SI -> ComInfo data area
;*
;*  OUTPUT:     none
;*
;*  EXIT-NORMAL: none
;*
;*  EXIT_ERROR:  none
;*
;*  EFFECTS:   ComInfo data area
;*
;*  INTERNAL REFERENCES:
;*      None
;*
;*  EXTERNAL REFERENCES: none
;*
;*********************** END OF SPECIFICATIONS **********************

Procedure DisableRemoteTXXO,HYBRID
        ASSUME cs:CSEG,ds:DSEG,es:NOTHING,ss:NOTHING

        mov     al,[si].ci_hsflag               ;cj (al) = handshake flag
        test    al,HS_XON_PENDING               ;cj is XON pending?
        jz      drtxo5                          ;cj XON not pending

        and     [si].ci_hsflag,NOT HS_XON_PENDING     ;cj don't send the XON
        jmp     SHORT drtxxo                          ;cj in effect send XOFF

drtxo5: test    al,HS_XOFF_SENT                 ;cj was XOFF sent?
        jnz     drtxxo                          ;cj XOFF already sent

        or      [si].ci_hsflag,HS_XOFF_PENDING  ;cj show XOFF must be sent
drtxxo:
        ret                                     ;cj

EndProc DisableRemoteTXXO


;**     EnableRemoteTX - enable the remote transmissions
;
;       EnableRemoteTX enables the remote transmitter using whatever
;       handshaking mode(s) are currently active. That is, it will enable
;       the hardware handshake lines and/or send an XON character.
;
;       ENTRY   (ds:si) -> ComInfo
;
;       EXIT    interrupts enabled
;
;       USES    ax dx

; Declare as hybrid so that ESP-specific can call as FAR,
; and non-ESP code can call as NEAR.
Procedure EnableRemoteTX,HYBRID
        ASSUME cs:CSEG,ds:DSEG,es:NOTHING,ss:NOTHING

        ChkComInfoPtr

        mov     ah,[si].ci_hhslines     ; (ah) = hardware handshake lines
        cli
        or      ah,ah                   ; check for HS lines to enable
        jz      ert4                    ; no handshake lines to enable

        call    EnableRemoteTXHS        ;cj go raise HS lines

ert4:   test    [si].ci_dcb_flags2,F2_IN_XO   ; is input flow control enabled?
        jz      ertx                          ; jump if not

        call    EnableRemoteTXXO        ;cj go send XON

        call    CheckTX                 ; enable TX

ertx:   sti

        ret

EndProc EnableRemoteTX

;********************** START OF SPECIFICATIONS *********************
;*
;*  SUBROUTINE NAME:    EnableRemoteTXHS
;*
;*  DESCRIPTIVE NAME:   Raise input handshaking lines
;*
;*  FUNCTION:   Set Modem Control to enable input HS lines.
;*
;*  ENTRY POINT:  EnableRemoteTXHS
;*
;*  LINKAGE:    Near
;*
;*  USES:       AX
;*
;*  INPUT:      DS:SI -> ComInfo data area
;*
;*  OUTPUT:     none
;*
;*  EXIT-NORMAL: none
;*
;*  EXIT_ERROR:  none
;*
;*  EFFECTS:   ComInfo data area
;*
;*  INTERNAL REFERENCES:
;*      None
;*
;*  EXTERNAL REFERENCES: none
;*
;*********************** END OF SPECIFICATIONS **********************

Procedure EnableRemoteTXHS,HYBRID
        ASSUME cs:CSEG,ds:DSEG,es:NOTHING,ss:NOTHING

        mov     ah,[si].ci_hhslines     ;cj (ah) = hardware handshake lines
        mov     dx,[si].ci_port         ;cj
        add     dx,R_MODMC              ;cj (dx) -> modem control reg.
        min     al,dx                   ;cj (al) = current MCR values
        or      al,ah                   ;cj turn on hardware handshake lines
        mout    dx,al                   ;cj

        ret                             ;cj

EndProc EnableRemoteTXHS

;********************** START OF SPECIFICATIONS *********************
;*
;*  SUBROUTINE NAME:    EnableRemoteTXXO
;*
;*  DESCRIPTIVE NAME:   Enable the remote transmissions
;*
;*  FUNCTION:   If XOFF was previously sent, send XON.
;*
;*  ENTRY POINT:  EnableRemoteTXXO
;*
;*  LINKAGE:    Near
;*
;*  USES:       AX
;*
;*  INPUT:      DS:SI -> ComInfo data area
;*
;*  OUTPUT:     none
;*
;*  EXIT-NORMAL: none
;*
;*  EXIT_ERROR:  none
;*
;*  EFFECTS:   ComInfo data area
;*
;*  INTERNAL REFERENCES:
;*
;*  EXTERNAL REFERENCES: none
;*
;*********************** END OF SPECIFICATIONS **********************

Procedure EnableRemoteTXXO,HYBRID
        ASSUME cs:CSEG,ds:DSEG,es:NOTHING,ss:NOTHING

        and     [si].ci_hsflag,NOT HS_XOFF_PENDING ;cj turn off any XOFF pending
        test    [si].ci_hsflag,HS_XOFF_SENT        ;cj was XOFF sent?
        jz      ertxxo                             ;cj no jump
        or      [si].ci_hsflag,HS_XON_PENDING      ;cj XON must be sent

ertxxo:
        ret                                        ;cj

EndProc EnableRemoteTXXO


;**     ComputeHHS - compute hardware handshake signals
;
;       ComputeHHS performs a number of functions pertaining to
;       the setting of the hardware handshake lines.
;
;       May raise or lower DTR or RTS depending on flags1 and flags2
;       and the previous values of flags1 and flgas2.
;
;       Set hhslines and outhhslines depending on flags1 and flags2.
;
;       ENTRY   (ds:si) -> ComInfo
;               (bl) = previous flags1
;               (bh) = previous flags2
;
;       EXIT
;
;       USES    ax bx dx

; Declare as hybrid so that ESP-specific can call as FAR,
; and non-ESP code can call as NEAR.
Procedure ComputeHHS,HYBRID
        ASSUME cs:CSEG,ds:DSEG,es:NOTHING,ss:NOTHING

        ChkComInfoPtr


        ; Standard port: raise/lower RTS/DTR.
chh10:
        and     bl,F1_DTR_MASK                  ; old DTR bits from flags1
        and     bh,F2_RTS_MASK                  ; old RTS bits from flags2

        mov     dx,[si].ci_port
        add     dx,R_MODMC              ; (dx) -> modem control reg
        min     al,dx                   ; al = current modem control lines
        and     al,MC_DTR OR MC_RTS     ; mask bits of interest
        or      al,MC_OUT2              ; must be set to get interrupts on AT


        ; DTR processing
        .errnz  F1_DTR_DISABLE - 00000000b
        .errnz  F1_DTR_ENABLE  - 00000001b
        .errnz  F1_DTR_FLOW    - 00000010b
        .errnz  F1_DTR_INVALID - 00000011b
        .errnz  MC_DTR         - 00000001b

        mov     ah,[si].ci_dcb_flags1           ; ah = flags1
        and     ah,F1_DTR_MASK                  ; new DTR bits
        xchg    ah,bl                           ; bl = flags1
        cmp     ah,bl
        je      chh20                           ; no change to DTR

        test    bl,F1_DTR_FLOW
        jnz     chh20                           ; handshake line

        and     al,NOT MC_DTR                   ; clear DTR line
        or      al,bl                           ; raise DTR if enable


chh20:  ; RTS processing
        .errnz  F2_RTS_DISABLE - 00000000b
        .errnz  F2_RTS_ENABLE  - 01000000b
        .errnz  F2_RTS_FLOW    - 10000000b
        .errnz  F2_RTS_TOGGLE  - 11000000b
        .errnz  MC_RTS         - 00000010b

        mov     ah,[si].ci_dcb_flags2           ; ah = flags2
        and     ah,F2_RTS_MASK                  ; new RTS bits
        xchg    ah,bh                           ; bh = flags2
        cmp     ah,bh
        je      chh40                           ; no change to RTS

        cmp    bh,F2_RTS_FLOW                   ;                @71491
        jz     chh40                            ; handshake line @71491

        and     al,NOT MC_RTS                   ; clear RTS line
        rol     bh,3                            ; shift RTS enable into MC_RTS
        or      al,bh                           ; raise RTS if enable
        ror     bh,3                            ; shift RTS enable back


chh40:  mout    dx,al                           ; set modem control reg


chh50:
; Both standard and enhanced set handshake flags -- though only standard
; uses them for handshaking.
; Set input handshaking based on the values of DTR and RTS in ci_dcb_flags2.
        .errnz  F1_DTR_FLOW - 00000010b
        .errnz  F2_RTS_FLOW - 10000000b
        .errnz       MC_DTR - 00000001b
        .errnz       MC_RTS - 00000010b

        xor     al,al
        test    [si].ci_dcb_flags1,F1_DTR_FLOW   ;CP20PB727400
        jz      chh60                            ;CP20PB727400

        or      al,MC_DTR

chh60:
        mov     ah,[si].ci_dcb_flags2           ; ah = flags2  @76898
        and     ah,F2_RTS_MASK                  ; new RTS bits @76898
        cmp     ah,F2_RTS_FLOW                  ;CP20PB727400  @71491
        jnz     chh80                           ;CP20PB727400  @71491

        or      al,MC_RTS

chh80:  mov     [si].ci_hhslines,al


; Set output handshaking for DSR, CTS and DCD from ci_dcb_flags1.

        .errnz  F1_OUT_CTS_FLOW - 00001000b
        .errnz  F1_OUT_DSR_FLOW - 00010000b
        .errnz  F1_OUT_DCD_FLOW - 00100000b
        .errnz           MS_CTS - 00010000b
        .errnz           MS_DSR - 00100000b
        .errnz           MS_DCD - 10000000b

        mov     al,[si].ci_dcb_flags1
        shl     ax,3                            ; d8 = DCD, d7 = DSR, d6 = CTS
        shr     al,1                            ; d8 = DCD, d6 = DSR, d5 = CTS
        shr     ax,1                            ; d7 = DCD, d5 = DSR, d4 = CTS
        and     al,MS_DCD OR MS_DSR OR MS_CTS    ; mask bits of interest
        mov     [si].ci_outhhslines,al

        ret

EndProc ComputeHHS

;********************** START OF SPECIFICATIONS *********************
;*
;* SUBROUTINE NAME:  SetAlert
;*
;* DESCRIPTIVE NAME:  Set the SNA Generic Alert Flags
;*
;* FUNCTION:  Sets the SNA Generic Alert Flags (in ci_flagGA).  Checks
;*            the Hardware Handshake mode and the MSR bits (in the
;*            ComInfo MSR Shadow.
;*
;* NOTES:     ABIOS MUST BE PRESENT
;*            Called by WriteTick on Write TimeOut - ** INTs Disabled **
;*
;* ENTRY POINTS:  SetAlert
;*    LINKAGE:    Near CALL
;*
;* INPUT:     DS:SI --> ComInfo data area for this port
;*            ES:DI --> RequestPkt address
;*
;* EXIT-NORMAL: none
;*
;* EXIT_ERROR:  none
;*
;* EFFECTS:   Registers:  AX, BX, CX
;*            ComInfo data area  (ci_flagGA)
;*
;*            ci_flagGA  BIT DEFINITIONS:
;*              FGA_WRITE_TO_CTS        EQU     0000000000000001
;*              FGA_WRITE_TO_DSR        EQU     0000000000000010
;*              FGA_WRITE_TO_DCD        EQU     0000000000000100
;*              FGA_WRITE_TO_XOFF       EQU     0000000000001000
;*
;* INTERNAL REFERENCES: none
;*
;* EXTERNAL REFERENCES: none
;*
;*********************** END OF SPECIFICATIONS **********************


Procedure SetAlert,NEAR
        ASSUME cs:CSEG,ds:DSEG,es:NOTHING,ss:NOTHING

;**  We know there is a Write Timeout on entry - now find out why.
;    We also know INTs are DISABLED, so that no modem INTs can occur here.
        push    cx
        xor     cx,cx                           ; prepare temp flagGA word

        mov     al,[si].ci_dcb_flags1           ; load 1st DCB flag word
        mov     dl,[si].ci_msrshadow            ; load MSR shadow from CIDA
        test    al,F1_OUT_CTS_FLOW              ; CTS output handshaking on?
        jz      trydsr                          ; no, go check DSR

        test    dl,MS_CTS                       ; is CTS down now?
        jnz     trydsr                          ; no, go check DSR

        or      cx,FGA_WRITE_TO_CTS             ; Yes, so set CTS Alert bit

trydsr:
        test    al,F1_OUT_DSR_FLOW              ; DSR output handshaking on?
        jz      trydcd                          ; no, go check DCD

        test    dl,MS_DSR                       ; is DSR down now?
        jnz     trydcd                          ; no, go check DCD

        or      cx,FGA_WRITE_TO_DSR             ; Yes, so set DSR Alert bit

trydcd:
        test    al,F1_OUT_DCD_FLOW              ; DCD output handshaking on?
        jz      tryxoff                         ; no, go check XOFF

        test    dl,MS_DCD                       ; is DCD down now?
        jnz     tryxoff                         ; no, go check XOFF

        or      cx,FGA_WRITE_TO_DCD             ; Yes, so set DCD Alert bit

tryxoff:
        mov     al,[si].ci_dcb_flags2           ; load 2nd DCB flag word
        test    al,F2_OUT_XO                    ; XON/XOFF flow control on?
        jz      sax                             ; no, go to exit

        test    [si].ci_hsflag,HS_XOFF_RECEIVED ; Was XOFF received?
        jz      sax                             ; no, go to exit

        or      cx,FGA_WRITE_TO_XOFF            ; Yes, so set XOFF Alert bit

sax:    mov     [si].ci_flagGA,cx               ; save the alert flags
        pop     cx
        ret                                     ; and return

EndProc SetAlert

;**     SetLineC - set the line control values
;
;       SetLineC sets the byte size, parity and number of stop bits
;       according to the value in (al).
;
;       ENTRY   (ds:si) -> ComInfo
;               (al) = new LCR value
;
;       EXIT
;
;       USES    al bx dx

; Declare as hybrid so that ESP-specific can call as FAR,
; and non-ESP code can call as NEAR.
Procedure SetLineC,HYBRID
        ASSUME cs:CSEG,ds:DSEG,es:NOTHING,ss:NOTHING

        ChkComInfoPtr

        and     al,LC_MASK              ; clear divisor latch bit in new LCR

        mov     dx,[si].ci_port
        add     dx,R_LINEC              ; (dx) = line control reg.
        mout    dx,al                   ; set LCR

        ; Now update ComInfo block
sl20:   mov     [si].ci_linec,al        ; save new LCR value
        mov     bx,ax
        and     bx,LC_BMASK             ; (bx) = byte size - 5
        mov     al,MaskTab[bx]          ; (al) = byte mask
        mov     [si].ci_cmask,al        ; save new byte mask

        ret

EndProc SetLineC


;**     CheckLCR - check the line control configuration
;
;       CheckLCR verifies the validity of the three line control
;       parameters, character size, number of stop bits and parity.
;       If the values are valid the new LCR is returned in (al).
;
;       ENTRY   (al) = byte size
;               (ah) = parity
;               (ch) = stop bits
;               ds:si -> ComInfo
;
;       EXIT    if 'C' clear
;                       (al) = value for line control reg
;               else 'C' set
;                       invalid line control
;
;       USES    ax cx

; Declare as hybrid so that ESP-specific can call as FAR,
; and non-ESP code can call as NEAR.
Procedure CheckLCR,HYBRID
        ASSUME cs:CSEG,ds:DSEG,es:NOTHING,ss:NOTHING

; Set up byte size.

        cmp     al,8
        ja      clcrx                   ; byte size out of range

        sub     al,5                    ; shift byte size to bits 0 and 1
        .errnz  LC_BMASK-00000011b      ; byte size must be these bits
        jc      clcrx                   ; byte size out of range

; al == 0, 5 data bits
; al == 1, 6 data bits
; al == 2, 7 data bits
; al == 3, 8 data bits

; Set up number of stop bits.
; ch == 0, 1 stop bits
; ch == 1, 1.5 stop bits if byte size is 5
; ch == 2, 2 stop bits if byte size is not 5

        or      ch,ch
        jz      clcr2                   ; 1 stop bit

        dec     ch
        jz      clcr0                   ; 1.5 stop bits

        dec     ch
        jnz     clcrx                   ; invalid number of stop bits

        or      al,al
        jz      clcrx                   ; 5 bit byte, 2 stop bits are invalid
        jmp     SHORT clcr1             ; not 5 bit byte, 2 stop bits is valid

clcr0:  or      al,al
        jnz     clcrx                   ; 1.5 stop with 6, 7 or 8 bit invalid

clcr1:  or      al,LC_STOP2             ; 1.5 or 2 stop bits

        .errnz  ONE_STOP_BIT-0
        .errnz  ONE_5_STOP_BITS-1
        .errnz  TWO_STOP_BITS-2
        .errnz  LC_BITS5

; Set up parity.

clcr2:  cmp     ah,SPACE_PARITY
        ja      clcrx                   ; parity out of range

        add     ah,ah                   ; map parity to 16450 ACE bits
        jz      clcr3                   ; 0=>0, 1=>1, 2=>3, 3=>5, 4=>7
        dec     ah

clcr3:  shl     ah,3                    ; Align with 16450 parity bits
        or      al,ah                   ; or into byte size

        .errnz  NO_PARITY-0
        .errnz       ODD_PARITY-1
        .errnz  EVEN_PARITY-2
        .errnz  MARK_PARITY-3
        .errnz  SPACE_PARITY-4

        ret

clcrx:  stc                             ; invalid LCR value
        ret

EndProc CheckLCR


;**     SetBaud - set the baud rete
;
;       SetBaud verifies that the baud rate is within the valid
;       baud range and can be supported within +-.01% (except 110
;       baud @ +.026% error, and 2000 baud @ -.69% error)
;       Converts the baud rate into the format necesary
;       by the hardware and sets the rate.
;       Also calls ComputeRTO and ComputeWTO to recalculate the timeouts.
;
;       ENTRY   (ds:si) -> ComInfo
;               (ax) = baud rate
;
;       EXIT    if 'C' clear
;                       ci_baud = baud rate
;                       divisor latch set in hardware
;               else
;                       invalid baud rate
;
;       USES    ax bx cx dx flags

; Declare as hybrid so that ESP-specific can call as FAR,
; and non-ESP code can call as NEAR.
Procedure SetBaud,HYBRID
        ASSUME cs:CSEG,ds:DSEG,es:NOTHING,ss:NOTHING

        ChkComInfoPtr

.386p
; verify within range of valid bauds
        cmp     eax,MINBAUD
        jb      sberr                   ; invalid baud rate

        test    [si].ci_flagx,FX_16550A ; chip type = 16550A
        jnz     sb05
        mov     ebx,MAX_SBAUD           ; max baud for standard port
        jmp     sb10
sb05:
        mov     ebx,MAX_EBAUD           ; max baud for enhanced port

sb10:   cmp     eax,ebx
        ja      sberr                   ; invalid baud rate

        ; convert baud rate to divisor and remainder
        mov     ebx,eax                 ; (ebx) = baud rate
        xor     edx,edx
        mov     eax, CLOCK_RATE
        div     ebx                     ; (eax) = divisor = clock / baud
                                        ; (edx) = remainder
        mov     ecx,eax                 ; (cx) = divisor

; special case to allow 110 baud without checking remainder
        cmp     ebx,110
        je      sb50                    ; 110 baud, ok

; verify that the baud rate can be supported within .01% by
; checking that the remainder is within the threshold
        cmp     edx,MAXREM
        jbe     sb50                    ; remainder within threshold, ok

; remainder too large on + side.  try on minus side
        inc     ecx                     ; (cx) = divisor + 1
        mov     eax,ecx                 ; (ax) = divisor + 1
        mul     ebx                     ; (dx:ax) = (divisor+1) * baud

; special case to allow 2000 baud without checking remainder
; (must be done here cuz 2000 is closer on the minus side!)
        cmp     ebx,2000
        je      sb50                    ; 2000 baud, ok

        sbb     eax, CLOCK_RATE         ; (eax) = (divisor+1) * baud - clock
                                        ;       = remainder
        jnz    sberr                    ; remainder high not zero, invalid baud
        cmp    eax,MAXREM
        ja     sberr                    ; remainder too large, invalid baud

sb50:   ; the baud is ok.  (cx) = divisor


; If standard port, set divisor-latch access bit (DLAB) in line control reg.
        pushf                           ; save flags
        cli                             ; disable interrupts while setting baud

        mov     dx,[si].ci_port
        add     dx,R_LINEC              ; (dx) -> LCR
        min     al,dx                   ; (al) = current value of LCR
        mov     ah,al                   ; save original state of LCR
        or      al,LC_DLAB              ; turn on DLAB
        mout    dx,al

; Set divisor-latch value.
; (dx) -> R_LINEC

        add     dx,R_BAUDH-R_LINEC      ; (dx) -> MSB of baud latch
        mov     al,ch                   ; (al) = divisor latch MSB
        mout    dx,al
        dec     dx                      ; (dx) -> LSB of baud latch
        mov     al,cl                   ; (al) = divisor latch LSB
        mout    dx,al                   ; set LSB of baud latch

; Restore original state of LCR (turn off DLAB)
; (dx) -> R_BAUDL

        add     dx,R_LINEC-R_BAUDL      ; (dx) -> LCR
        mov     al,ah                   ; (al) = original state of LCR
        mout    dx,al

        mov     [si].ci_baud,ebx         ; ci_baud = baud

        test    [si].ci_mult_COMs_IRQ, INT_SHARING ; sharing this IRQ line?
                                        ;  with another COM port?
        jz      sb60                    ; N: continue
        or      [si].ci_Flagx1,FX1_SET_BAUD_IP  ; Y: set "in progress" flag
        call    DeqComInfo              ; Y: remove COM port from linked list
        call    EnqComInfo              ; then put it back in baud rate order
        and     [si].ci_Flagx1,NOT FX1_SET_BAUD_IP ; clear "in progress" flag

sb60:
.286p
        call    ComputeWTO              ; re-compute write timeout value
        call    ComputeRTO              ; re-compute read  timeout value

        popff                           ; restore flags (interrupts)
        clc                             ; valid baud rate
        jmp     sbx


sberr:  stc                             ; invalid baud rate
sbx:    ret

EndProc SetBaud


;**     StartNextWRP - start the next write request packet
;
;       StartNextWRP pulls the next request off of the write request
;       list and makes it the current request.  In order to satisfy
;       this request we then attempt to enable the TX interrupt.
;
;       ENTRY   (ds:si) -> ComInfo
;
;       EXIT    if 'C' clear
;                       next request is now current AND ready to TX
;               else
;                       no requests outstanding OR not ready to TX
;
;       USES    ax bx

Procedure StartNextWRP,NEAR
        ASSUME cs:CSEG,ds:DSEG,es:NOTHING,ss:NOTHING

        ChkComInfoPtr

        cmp     [si].ci_w_rp._hi,0
        je      snw1                    ; current request

        ComErr  <StartNextWRP : already a current request>


snw1:   SaveReg         <si>
        lea     si,[si].ci_w_rpl        ; (ds:si) -> write request packet list
        call    UnLinkHeadRP            ; (es:di) -> request packet
        RestoreReg      <si>            ; (ds:si) -> ComInfo structure
        SaveReg         <di>            ; save request pkt ptr
        jc      snwx                    ; there is no request to start

        ChkRPPtr
        ChkRPType       CMDOUTPUT


        ; set up GDT selector which we allocated at init time
        ;; PhysToGDTSel (ComInfo[si].GDTSelWrite, physical @, length)
        ;; move ComInfo[si].GDTSelWrite to Physical @. _hi
        ;; move 0 to Physical @. _lo
        push    si
        mov     si,[si].ci_GDTSelWrite
        ; es:di -> request packet
        call    SetUpGDT
        pop     si

        mov     cx,es:[di].IOcount      ; (cx) = requested bytes
        call    WriteQueue              ; returns (cx) = bytes not enqueued
                                        ; updates ci_w_to_move

        cli

        mov     [si].ci_w_rp._hi,es     ; save request packet pointer
        mov     [si].ci_w_rp._lo,di

        mov     cx,[si].ci_w_to_start   ; initialize request time out value
        mov     [si].ci_w_to,cx


snwx:   call    CheckTX                 ; adjust TX empty interrupt
        sti
        RestoreReg      <di>            ; restore request pkt ptr
        ret

EndProc StartNextWRP


;**     StartNextRRP - start the next read request packet
;
;       StartNextRRP pulls the next request off of the read request
;       list and makes it the current request. If there is enough data
;       in the input queue to satisfy this new current request we
;       immediately run it.
;
;       ENTRY   (ds:si) -> ComInfo
;
;       EXIT    interrupts enabled
;
;       USES    ax bx dx

Procedure StartNextRRP,NEAR
        ASSUME cs:CSEG,ds:DSEG,es:NOTHING,ss:NOTHING

        ChkComInfoPtr

        cli

        cmp     [si].ci_r_rp._hi,0
        je      snr10                   ; there is no current request

        ComErr  <StartNextRRP : already a current request>

snr10:  SaveReg         <si>
        lea     si,[si].ci_r_rpl        ; (ds:si) -> read request packet list
        call    UnLinkHeadRP            ; (es:di) -> request packet
        RestoreReg      <si>            ; (ds:si) -> ComInfo structure
        jc      snrx                    ; there is no current request to start

        ChkRPPtr
        ChkRPType       CMDINPUT

; We have a request.

        mov     bx,es:[di].IOcount      ; initialize number of bytes to move
        mov     [si].ci_r_to_move,bx

        mov     bx,[si].ci_r_to_start   ; initialize time out value
        mov     [si].ci_r_to,bx

        ; set up GDT selector which we allocated at init time
        ;; PhysToGDTSel (ComInfo[si].GDTSelRead, physical @, length)
        ;; move ComInfo[si].GDTSelRead to Physical @. _hi
        ;; move 0 to Physical @. _lo
        push    si
        mov     si,[si].ci_GDTSelRead
        ; es:di -> request packet
        call    SetUpGDT
        pop     si

        mov     [si].ci_r_rp._hi,es     ; save request packet
        mov     [si].ci_r_rp._lo,di

        mov     bx,es:[di].IOcount      ; (bx) = number requested
        cmp     [si].ci_qin.ioq_count,bx
        jae     snr30                   ; enough to satisfy the request

        mov     al,[si].ci_dcb_flags3
        and     al,F3_READ_TO_MASK      ; (al) = read timeout mode

        cmp     al,F3_READ_TO_NW
        jne     snrx                    ; NOT no wait mode

snr30:  sti
        call    ProcRun                 ; run the guy

snrx:   sti
        ret

EndProc StartNextRRP


;**     LinkRP - link a request packet onto the end of a list
;
;       LinkRP takes a pointer to an RP_List and a pointer
;       to a request packet and adds the packet to the end
;       of the RP_List.
;
;       ENTRY   (ds:si) -> RP_List
;               (es:di) -> request packet
;
;       EXIT
;
;       USES    NONE

; Declare as hybrid so that ESP-specific can call as FAR,
; and non-ESP code can call as NEAR.
Procedure LinkRP,HYBRID
        ASSUME cs:CSEG,ds:DSEG,es:NOTHING,ss:NOTHING

        ChkRPLPtr
        ChkRPPtr

        mov     es:[di].PktDevLink._hi,0        ; NULLify the RP link

        pushf
        cli

        cmp     [si].rpl_head._hi,0
        jne     lrp1                    ; list is not empty

        mov     [si].rpl_head._hi,es    ; initialize head pointer
        mov     [si].rpl_head._lo,di
        mov     [si].rpl_tail._hi,es    ; initialize tail pointer
        mov     [si].rpl_tail._lo,di
        jmp     SHORT lrpx

lrp1:   SaveReg         <ds,si>

        lds     si,[si].rpl_tail        ; (ds:di) -> last RP in list

        mov     [si].PktDevLink._hi,es  ; add the RP to the list
        mov     [si].PktDevLink._lo,di

        RestoreReg      <si,ds>         ; (ds:si) -> RP_List
        ASSUME  ds:NOTHING

        mov     [si].rpl_tail._hi,es    ; update the tail pointer
        mov     [si].rpl_tail._lo,di

lrpx:   POPFF

        ret

EndProc LinkRP


;**     UnLinkHeadRP - unlink a request packet from the head of a list
;
;       UnLinkHeadRP removes the first request packet
;       from an RP_List if it the list not empty.
;
;       ENTRY   (ds:si) -> RP_List
;
;       EXIT    if 'C' clear
;                       (es:di) -> request packet
;               else
;                       list is empty
;
;       USES    ax es di carry

; Declare as hybrid so that ESP-specific can call as FAR,
; and non-ESP code can call as NEAR.
Procedure UnLinkHeadRP,HYBRID
        ASSUME cs:CSEG,ds:DSEG,es:NOTHING,ss:NOTHING

        ChkRPLPtr

        stc                                     ; assume the list is empty
        pushf                                   ; save flags
        cli

        cmp     [si].rpl_head._hi,0
        jz      ulhx                            ; list is empty

        les     di,[si].rpl_head                ; (es:di) -> RP

        mov     ax,es:[di].PktDevLink._hi       ; update head pointer
        mov     [si].rpl_head._hi,ax
        mov     ax,es:[di].PktDevLink._lo
        mov     [si].rpl_head._lo,ax

        popff                                   ; restore flags
        clc                                     ; no carry
        ret

ulhx:   popff                                   ; restore flags
        ret

EndProc UnLinkHeadRP


;**     UnLinkRP - unlink a specific request packet from a list
;
;       UnLinkRP removes a specific request packet from anywhere in
;       an RP_List. It is considered an inconsistent state if
;       the specified request is not on the list.
;
;       ENTRY   (ds:si) -> RP_List
;               (es:di) -> request packet
;
;       EXIT
;
;       USES    ax bx

; Declare as hybrid so that ESP-specific can call as FAR,
; and non-ESP code can call as NEAR.
Procedure UnLinkRP,HYBRID
        ASSUME cs:CSEG,ds:DSEG,es:NOTHING,ss:NOTHING

        ChkRPLPtr
        ChkRPPtr

        pushf                                   ; save flags
        cli

IFDEF RPLSTRICT
        cmp     [si].rpl_head._hi,0
        jnz     ulr0_1                  ; not end of list

        ComErr  <UnLinkRP : NULL list>
ulr0_1:
ENDIF
        mov     ax,es
        cmp     [si].rpl_head._hi,ax
        jne     ulr0                            ; not removing the head

        cmp     [si].rpl_head._lo,di
        jne     ulr0                            ; not removing the head

        mov     ax,es:[di].PktDevLink._hi       ; remove head
        mov     [si].rpl_head._hi,ax
        mov     ax,es:[di].PktDevLink._lo
        mov     [si].rpl_head._lo,ax
        jmp     SHORT ulrx

ulr0:   SaveReg         <ds,si>

        lds     si,[si].rpl_head                ; (ds:si) -> first RP

ulr1:

; If we get to the end of the list without finding the
; specified packet, we are in an inconsistent state.

        cmp     [si].PktDevLink._hi,0
        jne     ulr1_1                          ; not end of list

        ComErr  <UnLinkRP : end of list>

ulr1_1: mov     ax,es
        cmp     [si].PktDevLink._hi,ax
        jne     ulr2                            ; not the specified RP

        cmp     [si].PktDevLink._lo,di
        je      ulr3                            ; found the specified RP

ulr2:   lds     si,[si].PktDevLink              ; (ds:si) -> next RP
        jmp     SHORT ulr1

ulr3:   mov     ax,es:[di].PktDevLink._lo       ; remove specified RP
        mov     [si].PktDevLink._lo,ax
        mov     ax,es:[di].PktDevLink._hi
        mov     [si].PktDevLink._hi,ax

        or      ax,ax
        mov     ax,ds
        mov     bx,si                           ; (ax:bx) -> potential tail

        RestoreReg      <si,ds>                 ; (ds:si) -> RP_List
        ASSUME  ds:NOTHING

        jnz     ulrx                            ; did not remove tail
                                                ; cc set by above or instr.

        mov     [si].rpl_tail._hi,ax            ; update tail pointer
        mov     [si].rpl_tail._lo,bx

ulrx:   popff
        ret

EndProc UnLinkRP


;**     ComputeRTO - compute the read timeout in timer ticks
;
;       ComputeRTO coverts the user specified read time out value from
;       hundredths of a second to system timer ticks. It also saves
;       this converted value in the ComInfo structure and initializes
;       the timeout countdown value.
;
;       ENTRY   (ds:si) -> ComInfo
;
;       EXIT
;
;       USES    ax dx
;
;       NOTE    Must be called with interrupts off so an interrupt doesn't
;               come in while we are updating the timeout values.

; Declare as hybrid so that ESP-specific can call as FAR,
; and non-ESP code can call as NEAR.
Procedure ComputeRTO,HYBRID
        ASSUME cs:CSEG,ds:DSEG,es:NOTHING,ss:NOTHING

        ChkComInfoPtr

        mov     ax,[si].ci_dcb_readto       ; (ax) = 0.01 sec/timeout
        mov     dx,100                  ;        * 100
        mul     dx                      ; (dx:ax) = .0001 sec/timeout
        div     ClkIntrvl               ;         / .0001 sec/tick
                                        ; (ax) = ticks/timeout
        inc     ax                      ; round up
        cmp     ax,1                    ; is it only one tick?
        jne     crto10                  ;  no, ok
        inc     ax                      ;  yes, make it 2 ticks instead

crto10: mov     [si].ci_r_to_start,ax   ; save read timeout value
        mov     [si].ci_r_to,ax         ; initialize read countdown


        ; Compute ticks per RX interrupt and use if larger than user
        ; specified timeout.  This is required even if the FIFO is
        ; off if the system clock is fast and the baud rate is low.

        SaveReg <bx>
        mov     bl,[si].ci_dcb_flags3   ; (bl) = flags3
        .errnz  F3_RX_MASK - 01100000b
        and     bx,F3_RX_MASK           ; (bx) = rx trig level
        rol     bl,3                    ; (bx) = rx trig level index
        mov     bl,RxTrigTbl[bx]        ; (bx) = rx trig level value (char/int)

        call    ComputeTPI              ; (ax) = ticks/interrupt
        RestoreReg <bx>

        cmp     ax,[si].ci_r_to_start   ; if ax > read timeout
        jbe     crtox

        mov     [si].ci_r_to_start,ax   ; save read timeout value
        mov     [si].ci_r_to,ax         ; initialize read countdown

crtox:  ret

EndProc ComputeRTO


;**     ComputeWTO - compute the write timeout in timer ticks
;
;       ComputeWTO coverts the user specified write time out value from
;       hundredths of a second to system timer ticks. It also saves
;       this converted value in the ComInfo structure and initializes
;       the timeout countdown value.
;
;       ENTRY   (ds:si) -> ComInfo
;
;       EXIT
;
;       USES    ax dx
;
;       NOTE    Must be called with interrupts off so an interrupt doesn't
;               come in while we are updating the timeout values.

; Declare as hybrid so that ESP-specific can call as FAR,
; and non-ESP code can call as NEAR.
Procedure ComputeWTO,HYBRID
        ASSUME cs:CSEG,ds:DSEG,es:NOTHING,ss:NOTHING

        ChkComInfoPtr

        mov     ax,[si].ci_dcb_writeto  ; (ax) = 0.01 sec/timeout
        mov     dx,100                  ;        * 100
        mul     dx                      ; (dx:ax) = .0001 sec/timeout
        div     ClkIntrvl               ;         / .0001 sec/tick
                                        ; (ax) = ticks/timeout
        inc     ax                      ; round up
        cmp     ax,1                    ; is it only one tick?
        jne     cwto10                  ;  no, ok
        inc     ax                      ;  yes, make it 2 ticks instead

cwto10: mov     [si].ci_w_to_start,ax   ; save write timeout value
        mov     [si].ci_w_to,ax         ; initialize write countdown


        ; Compute ticks per TX interrupt and use if larger than user
        ; specified timeout.  This is required even if the FIFO is
        ; off if the system clock is fast and the baud rate is low.

        SaveReg <bx>
        mov     bx,1                            ; (bl) = 1  (char/int)
        test    [si].ci_dcb_flags3,F3_TX_16     ; 16 char/int TX?
        jz      cwto20                          ;  no, use 1
        mov     bx,16                           ; (bl) = 16 (char/int)

cwto20: call    ComputeTPI                      ; (ax) = ticks/interrupt
        RestoreReg <bx>

        cmp     ax,[si].ci_w_to_start           ; if ax > write timeout
        jbe     cwtox

        mov     [si].ci_w_to_start,ax           ; save write timeout value
        mov     [si].ci_w_to,ax                 ; initialize write countdown

cwtox:  ret

EndProc ComputeWTO


;**     ComputeTPI - compute ticks per interrupt
;
;       ComputeTPI computes the number of system timer ticks it takes
;       to transmit BX characters at the current baud rate.
;
;       ENTRY   (ds:si) -> ComInfo
;               (bx)     = chars/int (1, 4, 8 or 14 for RX; 1 or 16 for TX)
;
;       EXIT    (ax)     = ticks/interrupt
;
;       USES    ax bx dx

Procedure ComputeTPI,NEAR

        ASSUME cs:CSEG,ds:DSEG,es:NOTHING,ss:NOTHING

        ChkComInfoPtr    9

; Compute ticks per RX or TX interrupt and use if less than user
; specified timeout.  This is required even if the FIFO is off
; if the system clock is fast and the baud rate is low.
;
; in:   bits /sec       (baud) (2-19200)
;       sec  /tick      (system clock interval in .0001 sec units)
;                        (100-10000)
;       chars/int       (bx) (1-16)
;
; out:  ticks/int
;
;       char/int * 12 bits/char * 10000   bits/int
;       ------------------------------- = --------- = ticks/int
;       bits/sec * sec/tick               bits/tick
;
; BUGBUG 11-29-88 bryand - 12 bits per char - could calc exact
;
; BUGBUG 11-29-88 bryand - min clock interval = 30/10000 sec
;       the max numerator   = 16 * 12 * 10000 = 1920000
;       the min denominator = (1920000/65535) + 1 = 30
;       to make the quotient fit in 16 bits without a 32/32 divide.

.386p
        shl     bx,1                    ; (bx) = char/int * 2 (120000 = 2*60000)
        mov     eax,60000               ;           * 10000 * 6 bits/char
        mul     bx                      ; (edx:eax) = bits/int
                                        ;           / sec/tick
        div     ClkIntrvl               ; (ax) = bits/int * tick/sec
                                          ;        / bits/sec
        xor     edx,edx                 ; clear remainder
        div     [si].ci_baud            ; (ax) = ticks/int
        inc     eax                     ; (ax) = ticks/int (rounded up)

.286p
        ret

EndProc ComputeTPI


;**     Get_OCI_Sem - get the open/close/ioctl semaphore
;
;       Get the open/close/ioctl semaphore.
;       If cannot get semaphore jump to CmxGenFail.
;       If we got the sem, but the device has shut down, free the sem
;       (so all blockers will wake up) and jump to CmxGenFail.
;
;       ENTRY   (ds:si) -> cominfo
;
;       EXIT    No Carry - got semaphore
;
;               Carry    - couldn't get semaphore or driver shut down,
;                          jmp CmxGenFail
;
;       USES    ax bx cx dx

Procedure Get_OCI_Sem,NEAR
        ASSUME cs:CSEG,ds:NOTHING,es:NOTHING,ss:NOTHING

        ChkComInfoPtr

        SaveReg         <di>
        mov     ax,ds
        lea     bx,[si].ci_oci_sem              ; (ax:bx) -> semaphore
        mov     cx,-1
        mov     di,cx                           ; (cx:di) = time out
        mov     dl,DevHlp_SemRequest
        DevHelp
        RestoreReg      <di>
        jc      gosf                            ; error, couldn't get semaphore

        ChkComInfoPtr

        ; got the semaphore
        test    Flags,F_SHUT_DOWN
        jz      gosx                            ; driver is not shut down

        ; got the semaphore, but driver is shut down so clear the sem and fail
        mov     ax,ds
        lea     bx,[si].ci_oci_sem              ; (ax:bx) -> semaphore
        mov     dl,DevHlp_SemClear
        DevHelp

gosgenfail:
        jmp     CmxGenFail                      ; error, couldn't get semaphore

gosx:   mov     [si].ci_oci_sem_own._hi,es      ; save oci_sem owner
        mov     [si].ci_oci_sem_own._lo,di
        ret

gosf:   cmp     ax,ERROR_INTERRUPT
        jne     gosgenfail                      ; not interrupted, genfail
        jmp     CmxInterrupted                  ; SemRequest Interrupted

EndProc Get_OCI_Sem


;**     ComError - com driver internal error
;
;       ComError is used to shut down the device driver in cases
;       of absolute failure. It should be invoked by the ComErr macro.
;
;       Actions taken:
;               set F_SHUT_DOWN flag to show driver is sutting down
;               UnsetTimer
;               UnsetIRQ for open ports
;               ProcRun all threads blocked in the driver
;               zap ComInfo offsets to fail subsequent requests
;
;       ENTRY   pointer to string       (WORD) <- TOS
;
;       EXIT    (ds) -> DD Data seg
;               if unrecoverable error  - int 3
;               if task time            - jmp CmxGenFail
;               if interrupt time       - jmp cinterr
;
;       USES    ax bx cx dx es di
;
;       WARNING It is assumed that either ds or es contains the device
;               drivers DATA segment.

; Declare as hybrid so that ESP-specific can call as FAR,
; and non-ESP code can call as NEAR.
Procedure ComError,HYBRID
        ASSUME cs:CSEG,ds:NOTHING,es:NOTHING,ss:NOTHING

; Get addressabilty to our DATA segment

        ; verify ds before using to prevent trap if DATA is in es
        ; don't bother verifying es as we trap if DATA is not in es anyway

        mov     ax,ds                   ; (ax) = ds
        verifywrite     ax
        jnz     cer5                    ; ds not writable data segment, try es

        loadsl  ax,ax                   ; (ax) = ds segment limit
        cmp     ax,OFFSET DataSegSig+1  ; +1 for 2nd byte of signature
        jb      cer5                    ; ds segment limit too small, try es

        cmp     ds:DataSegSig,SIGNATURE
        je      cer10                   ; (ds) = DATA

cer5:   cmp     es:DataSegSig,SIGNATURE
        ljne    cerxx                   ; DATA unavailable
        push    es
        pop     ds                      ; (ds) = DATA
        ASSUME  ds:DSEG

cer10:  cli                             ; no interrupts while checking flags
        test    Flags,F_SHUT_DOWN
        ljnz    cerxx                   ; nested internal error, trap

        or      Flags,F_SHUT_DOWN       ; flag shut down in progress

        push    si                      ; save current ComInfo
        push    Com4                    ; save Com2 cominfo
        push    Com3                    ; save Com2 cominfo
        push    Com2                    ; save Com2 cominfo
        mov     si,Com1                 ; (ds:si) -> Com1 cominfo

        ; zeroing the Comn pointer values will make subsequent requests
        ; because the strategy routine will think the port is not installed.
        ; subsequent interrupts (before we finish calling ShutDownPort below)
        ; will fail in the same way.

        mov     Com1,0          ; zap Com1 Info
        mov     Com2,0          ; zap Com2 Info
        mov     Com3,0          ; zap Com2 Info
        mov     Com4,0          ; zap Com2 Info

        sti

        cmp     si,0
        je      sd_com2
        call    ShutDownPort            ; shut down Com1

sd_com2:
        pop     si                      ; (ds:si) -> Com2 ComInfo
        cmp     si,0
        je      sd_com3
        call    ShutDownPort            ; shut down Com2

sd_com3:
        pop     si                      ; (ds:si) -> Com3 ComInfo
        cmp     si,0
        je      sd_com4
        call    ShutDownPort            ; shut down Com3

sd_com4:
        pop     si                      ; (ds:si) -> Com4 ComInfo
        cmp     si,0
        je      sd_comx
        call    ShutDownPort            ; shut down Com4

sd_comx:
        mov     ax,OFFSET Ticker
        mov     dl,DevHlp_ResetTimer
        DevHelp

        ; ProcRun requests on the ready list
        mov     si,OFFSET Ready ; (ds:si) -> Ready list

cer70:  call    UnLinkHeadRP    ; (es:di) -> runnable request packet
        jc      cer80           ; no more to run
        call    ProcRun
        jmp     SHORT cer70     ; try to run another

cer80:
        pop     si                      ; (ds:si) -> current Cominfo
        ChkComInfoPtr

        cmp     [si].ci_depth,D_NONE
        jne     cerxx           ; interrupt time

        jmp     CmxGenFail      ; task time => GenFail


cerxx:
        ret

EndProc ComError


;** ShutDownPort - shut down a com port if it was open
;
;       if the port is open
;         UnsetIRQ
;         ProcRun requests on the ready list
;         flush write requests
;         flush read requests
;         SemClear open/close/ioctl semaphore
;
;       ENTRY   (ds:si) -> ComInfo of port to shut down
;
;       EXIT    NONE
;
;       USES    ax bx cx dx es di

Procedure ShutDownPort,NEAR
        ASSUME cs:CSEG,ds:NOTHING,es:NOTHING,ss:NOTHING

        or      si,si
        lje     sdpx                    ; device not installed

        ChkComInfoPtr

        cmp     [si].ci_nopens,0
        lje     sdpx                    ; device not open


sdpx10:
        ; Standard port: disable interrupts from UART, drop modem lines and
        ; clear break
        mov     dx,[si].ci_port
        add     dx,R_INTEN              ; (dx) -> interruprt enable reg
        xor     al,al                   ; (al) =  0
        mout    dx,al                   ; disable all com interrupts

        add     dx,R_MODMC-R_INTEN      ; (dx) -> modem control reg
        mout    dx,al                   ; turn off all MCR signals

        mov     al,[si].ci_linec        ; (al) = line control value
        and     al,NOT LC_BREAK     ; turn off break
        and     [si].ci_hsflag, NOT HS_BREAK_SET
                                        ; break no longer set
        call    SetLineC

        test    [si].ci_mult_COMs_IRQ, INT_SHARING  ; sharing the IRQ?
                                                ; with other COM ports?
        jz      sdpx15                          ; N: unset normally
        call    DeqComInfo                      ; Y: remove from queue
        jmp     short sdpx30                    ; then continue

sdpx15: mov     bl, [si].ci_irq
        jmp     SHORT @f

sdpx20:  mov     bl,[bx].pi_irq

@@:     xor     bh,bh
        mov     dl,DevHlp_UnSetIRQ
        DevHelp                       ; release the IRQ

sdpx30:  call    ComWFlushSub            ; flush all outstanding write requests
        call    ComRFlushSub            ; flush all outstanding read  requests

        mov     ax,ds
        lea     bx,[si].ci_oci_sem      ; (ax:bx) -> semaphore
        mov     dl,DevHlp_SemClear
        DevHelp                         ; clear sem to run threads waiting on it

sdpx:   ret

EndProc ShutDownPort


;**
;       The following procedures are conditionally assembled based
;       on the definition of a number of assembler defines.
;       They are used as a debugging aid.

IFDEF   CISTRICT

;**     CheckComInfoPtr - check the validity of the ComInfo pointer
;
;       CheckComInfoPtr is a debugging routine which will attempt
;       to verify that (ds:si) points to a valid ComInfo structure.
;       It does this verification by checking for a SIGNATURE
;       in ci_signature.
;
;       To invoke this procedure, use the ChkComInfoPtr macro which
;       is also conditionally assembled.
;
;       ENTRY   (ds:si) -> ComInfo
;
;       EXIT    If (ds:si) is invalid
;                       ComError
;               else
;                       return
;
;       USES    NONE

Procedure CheckComInfoPtr,NEAR
        ASSUME cs:CSEG,ds:DSEG,es:NOTHING,ss:NOTHING

        pushf
        cmp     [si].ci_signature,SIGNATURE
        jne     cip1                            ; invalid pointer

        POPFF
        ret

cip1:   ComErr  <ChkComInfoPtr : invalid signature.>

EndProc CheckComInfoPtr

ENDIF   ; CISTRICT


IFDEF   RPSTRICT

;**     CheckRPPtr - check the validity of the request packet pointer
;
;       CheckRPPtr is a debugging routine which will attempt
;       to verify that (es:di) points to a valid request packet.
;       It does this verification by checking for a SIGNATURE
;       in PktStatus.
;
;       To invoke this procedure, use the ChkRPPtr macro which
;       is also conditionally assembled.
;
;       ENTRY   (es:di) -> request packet
;
;       EXIT    If (es:di) is invalid
;                       ComErr
;               else
;                       return
;
;       USES    NONE

Procedure CheckRPPtr,NEAR
        ASSUME cs:CSEG,ds:DSEG,es:NOTHING,ss:NOTHING

;       the SIGNATURE can't use the STDON or open_monitor bits
        .errnz  SIGNATURE AND STDON
        .errnz  SIGNATURE AND open_monitor

        pushf
        SaveReg         <ax>
        mov     ax,es:[di].PktStatus
        and     ax,NOT (STDON OR open_monitor)          ; ignore STDON bit
        cmp     ax,SIGNATURE
        RestoreReg      <ax>
        jne     crp1                                    ; invalid pointer

        POPFF
        ret

crp1:   ComErr  <ChkRPPtr : invalid signature.>

EndProc CheckRPPtr

ENDIF   ; RPSTRICT


IFDEF   RPLSTRICT

;**     CheckRPLPtr - check the validity of the RP_List pointer
;
;       CheckRPLPtr is a debugging routine which will attempt
;       to verify that (ds:si) points to a valid RP_List.
;       It does this verification by checking for a SIGNATURE
;       in rpl_head.
;
;       To invoke this procedure, use the ChkRPLPtr macro which
;       is also conditionally assembled.
;
;       ENTRY   (ds:si) -> RPL_List
;
;       EXIT    If (ds:si) is invalid
;                       ComErr
;               else
;                       return
;
;       USES    NONE

Procedure CheckRPLPtr,NEAR
        ASSUME cs:CSEG,ds:DSEG,es:NOTHING,ss:NOTHING

        pushf
        cmp     [si].rpl_signature,SIGNATURE
        jne     crpl1                   ; invalid pointer

        POPFF
        ret

crpl1:  ComErr  <ChkRPLPtr : invalid signature.>

EndProc CheckRPLPtr

ENDIF   ; RPLSTRICT

;**     EnqComInfo - Enqueue a ComInfo structure
;
;       EnqComInfo updates the data structures used to
;       support IRQ sharing.  For each shared IRQ line, a
;       linked list of ComInfo structures is maintained in baud
;       rate order.  On an Open of a COM port that shares its
;       IRQ line, this routine is called to insert the ComInfo
;       structure into the linked list.  This is a standard
;       "walk the chain" algorithm that searches a uni-directional
;       linked list for the spot to insert the new entry.
;
;       ENTRY   (ds:si) -> ComInfo structure
;
;       EXIT    Linked list updated
;               CY = 0, no error
;               CY = 1, error
;
;       USES    Flags

Procedure EnqComInfo,NEAR
        ASSUME cs:CSEG,ds:DSEG,es:NOTHING,ss:NOTHING

        pusha

        mov     bx, [si].ci_int_data    ; IRQ line data for this COM port
        cmp     ShrdIRQ1[bx].si_firstCOM, 0     ; chain currently empty?
        jne     enq10                   ; N: we'll have to walk the chain
        mov     ShrdIRQ1[bx].si_firstCOM, si    ; Y: set the "head" pointer
        mov     [si].ci_next_COM, 0     ; indicate we are last in chain
        jmp     short enq50             ; then exit

enq10:
        mov     di, ShrdIRQ1[bx].si_firstCOM    ; first ComInfo in chain
        xor     cx, cx                  ; "back pointer" to insert location

enq20:
.386p
        mov     eax, [si].ci_baud       ; get candidate baud rate
        cmp     eax, [di].ci_baud       ; is it > than the chain entry's baud?
.286p
        jle     enq30                   ; N: keep walking the chain
        cmp     cx, 0                   ; Y: are we inserting at the top?
        jne     enq25                   ; N: just update the chain
        mov     ax, ShrdIRQ1[bx].si_firstCOM    ; Y: we'll have to update the "head" ptr
        mov     ShrdIRQ1[bx].si_firstCOM, si    ; update the "head" pointer
        mov     [si].ci_next_COM, ax    ; former first entry in chain
        jmp     short enq50             ; then exit

enq25:
        mov     [si].ci_next_COM, di    ; point to current chain entry
        mov     di, cx                  ; get "back pointer"
        mov     [di].ci_next_COM, si    ; get in front of current entry
        jmp     short enq50             ; then exit

enq30:
        cmp     [di].ci_next_COM, 0     ; are we at the end of the chain?
        jne     enq35                   ; N: keep searching
        mov     [di].ci_next_COM, si    ; Y: hook in at the end of the chain
        mov     [si].ci_next_COM, 0     ; and indicate "end of chain"
        jmp     short enq50             ; then exit

enq35:
        mov     cx, di                  ; update the "back pointer"
        mov     di, [di].ci_next_COM    ; next entry in the chain
        jmp     enq20                   ; and then try again

enq50:
        test    [si].ci_Flagx1, FX1_SET_BAUD_IP ; just changing the baud?
        jnz     enq60                   ; Y: skip the IRQ stuff and exit (CY cl)
        inc     ShrdIRQ1[bx].si_opens   ; N: increment the number of opens
        cmp     ShrdIRQ1[bx].si_opens, 1  ; first COM port on this IRQ?
        clc                             ;   assume no
        jne     enq60                   ; N: already have IRQ, just exit

        mov     ax, ShrdIRQ1[bx].si_entry ; offset of IRQ handler
        xor     bh,bh
        mov     bl, ShrdIRQ1[bx].si_irq ; IRQ line
        mov     dh, 1                   ; share the IRQ line
        mov     dl, DevHlp_SetIRQ
        push    es
        push    ds
        pop     es                      ; point to DSEG
        setDS   HSEG                    ; point to resident data
        call    es:[DevHlp]             ; register for the IRQ line, CY set on
                                        ;  error
        pop     es
        setDS   DSEG                    ; restore DS

enq60:
        popa
        ret                             ; with CY indicating success/failure

EndProc EnqComInfo

;**     DeqComInfo - Dequeue a ComInfo structure
;
;       DeqComInfo updates the data structures used to
;       support IRQ sharing.  For each shared IRQ line, a
;       linked list of ComInfo structures is maintained in baud
;       rate order.  On a Close of a COM port that shares its
;       IRQ line, this routine is called to remove the ComInfo
;       structure from the linked list.  This is a standard
;       "walk the chain" algorithm that searches a uni-directional
;       linked list for the entry to remove.
;
;       ENTRY   (ds:si) -> ComInfo structure
;
;       EXIT    Linked list updated
;
;       USES    Flags

Procedure DeqComInfo,NEAR
        ASSUME cs:CSEG,ds:DSEG,es:NOTHING,ss:NOTHING

        pusha

        mov     bx, [si].ci_int_data    ; IRQ line data for this COM port
        cmp     ShrdIRQ1[bx].si_firstCOM, si    ; Are we first in the chain?
        jne     deq10                   ; N: we'll have to walk the chain
        mov     ax, [si].ci_next_COM    ; Y: get addr. of entry following us
        mov     ShrdIRQ1[bx].si_firstCOM, ax    ; and update the "head" ptr.
        jmp     short deq50             ; then exit

deq10:
        mov     di, ShrdIRQ1[bx].si_firstCOM    ; first ComInfo in chain

deq20:
        cmp     si, [di].ci_next_COM    ; does this chain entry point to us?
        jne     deq30                   ; N: keep walking the chain
        mov     ax, [si].ci_next_COM    ; Y: get the ComInfo we point to
        mov     [di].ci_next_COM, ax    ; and remove us from the chain
        jmp     short deq50             ; then exit

deq30:
        mov     di, [di].ci_next_COM    ; get next entry in chain
        jmp     deq20                   ; and try again

deq50:
        test    [si].ci_Flagx1, FX1_SET_BAUD_IP ; just changing the baud?
        jnz     deq60                   ; Y: skip the IRQ stuff and exit
        dec     ShrdIRQ1[bx].si_opens   ; N: decrement the number of opens
        cmp     ShrdIRQ1[bx].si_opens, 0 ;  COM ports left?
        jne     deq60                   ; Y: just exit
        xor     bh,bh
        mov     bl, ShrdIRQ1[bx].si_irq ; get IRQ line
        mov     dl, DevHlp_UnSetIRQ
        push    es
        push    ds
        pop     es                      ; point to DSEG
        setDS   HSEG                    ; point to resident data
        call    es:[DevHlp]             ; release the IRQ line
        pop     es
        setDS   DSEG                    ; restore DS

deq60:
        popa
        ret

EndProc DeqComInfo

Procedure SetUpGDT, near
; si = selector to set up
; es:di -> request packet

        push    ax
        push    bx
        push    cx
        push    dx

        mov     ax,es:[di].IoPData._hi
        mov     bx,es:[di].IoPData._lo          ; (ax:bx)= physical xfer address
        mov     cx,es:[di].IOcount              ; (cx) = requested bytes
        mov     dx,DevHlp_PhysToGDTSelector
        call    [DevHlp]

        mov     es:[di].IoPData._hi,si
        mov     es:[di].IoPData._lo,0           ; physical xfer address = Sel:0

        pop     dx
        pop     cx
        pop     bx
        pop     ax

        ret
EndProc SetUpGDT

CSEG    ENDS

END
