;*DDK*************************************************************************/
;
; COPYRIGHT (C) Microsoft Corporation, 1989
; COPYRIGHT    Copyright (C) 1995 IBM Corporation
;
;    The following IBM OS/2 WARP source code is provided to you solely for
;    the purpose of assisting you in your development of OS/2 WARP device
;    drivers. You may use this code in accordance with the IBM License
;    Agreement provided in the IBM Device Driver Source Kit for OS/2. This
;    Copyright statement may not be removed.;
;*****************************************************************************/
;       SCCSID = @(#)atespisr.asm       6.3 91/04/22
; ***************************************************************************
; *
; *
; *
; ***************************************************************************

        PAGE    80,132
        .286p

        TITLE   com01.sys - Asynchronous Communication Device Driver
        NAME com01

;***    espisr.asm - Interrupt Handlers
;
;       ComInt          - Interrupt entry point
;       RxInt           - Receive FIFO trigger interrupt
;       TxInt           - Transmitter FIFO trigger interrupt
;       EMxInt          - Modem status interrupt
;
;       Modification History
;
;       ACW     04/16/91        @PVW Added perfview counters/timers
;       RAC     12/16/93  76699 Make Perfview Optionally compiled
;       WDM     04/21/94        82548 - pvwxport.inc now included in atcom.inc
;


.xlist
ifdef PERFVIEW
; 82548  include pvwxport.inc            ;@PVW
endif

include devhlp.inc
include devsym.inc
include basemaca.inc
include realmac.inc
include osmaca.inc
include error.inc
include protmode.inc
include atcom.inc
include atesp.inc
include devhlpP.inc            ;76711
.list

        EXTRNFAR        WriteQueue
        EXTRNFAR        WriteQueueByte
        EXTRNFAR        ReadQueueByte
        EXTRNFAR        ReadQueue
        EXTRNFAR        UnLinkHeadRP
        EXTRNFAR        ProcRun
        EXTRNFAR        GetNextWRP
        EXTRNFAR        ComError
        EXTRNFAR        LinkRP
        extrn           IssueESPCmd:near
        extrn           TriggerTX:near
HSEG SEGMENT
        EXTRN   tcount_ptr:WORD
        EXTRN   read_write_ptr:WORD
        EXTRN   alloc_ptr:WORD
        EXTRN   dealloc_ptr:WORD
        EXTRN   disable_ptr:WORD
HSEG ENDS

DSEG SEGMENT
        EXTRN   Com1:WORD
        EXTRN   Com2:WORD
        EXTRN   Com3:WORD
        EXTRN   Com4:WORD
        EXTRN   DevHlp:DWORD
        EXTRN   Flags:BYTE
        EXTRN   Ready:WORD
        EXTRN   Kernel_Type:WORD
        EXTRN   ESP1:WORD
        EXTRN   ESP2:WORD

RRMaskTable     DB      00000001b               ; Rx1FIFO int
                DB      00000010b               ; Tx1FIFO int
                DB      00010000b               ; Rx2FIFO int
                DB      00100000b               ; Tx2FIFO int
MAX_RR_INDEX    EQU     $ - RRMaskTable
RRIndex         DB      0

RRServiceTable  DW      Rx1Int
                DW      Tx1Int
                DW      Rx2Int
                DW      Tx2Int

public sid_buf,sid_buf_index,dma_buf
sid_buf_index   db      0
sid_buf         db      256 dup (0)
dma_buf         db      256 dup (0)

DSEG    ENDS

E_CSEG  SEGMENT
        ASSUME cs:E_CSEG,ds:DSEG,es:NOTHING,ss:NOTHING

;**     ESPInt- Interrupt Handlers for Enhanced Ports
;
;       NOTE    interrupt handlers do NOT need to save registers
;
;       ENTRY   ds = device driver data segment (set up by kernel)
;               interrupts disabled
;
;       EXIT    none
;
;       USES    none

Procedure ESPInt,FAR
        ASSUME cs:E_CSEG,ds:DSEG,es:NOTHING,ss:NOTHING
        jmp     short eint2

eint0:  ComErr  <ComInt: too many nested interrupts>,CALLFAR

;* eint00 - interrupt during first open or init
eint00: sti
        ; New service mask so no more ints from this port
        mov     di,[si].ci_pinfo
        mov     al,[di].pi_svcmask
        cmp     [si].ci_cmd_offset,0
        je      eint01
        CLR     al,ESP_SVC_MASK_PORT1
        jmp     SHORT eint02
eint01: CLR     al,ESP_SVC_MASK_PORT2
eint02: mov     [di].pi_svcmask,al
        SetSvcMask      al

        ; Signal EOI. Can't allow interrupts from now on because other
        ; ESP port might interrupt on this same IRQ and cause nesting.
        mov     di,[si].ci_pinfo
;       mov     al,[di].pi_irq  ; irq                         remove 76711
;       mov     dl,DevHlp_EOI           ; end of interrupt    remove 76711
        cli
;       DevHelp                                               remove 76711
        DevEOI    <byte ptr [di].pi_irq>,DevHlp  ;            add    76711
        jmp     eintx

;* eint000 - interrupt during driver shut down
eint000:
        sti
        mov     sp,bp           ; restore stack frame before leaving
        stc                     ; set carry - not my interrupt
        ret                     ; return carry to interrupt manager which
                                ; will issue EOI for me

eint2:
        mov     si,0                    ; (si) -> NULL
        SaveReg         <di>            ; save di (ESPInfo) on stack frame
        mov     bp,sp                   ; (bp)   -> stack frame
                                        ; (bp-2) -> di
        inc     [di].pi_depth           ; inc nested interrupt counter
 ;      jz      eint0        76711       ; too many nested interrupts   76711
        jnz     meg4                    ; too many nested interrupts   76711
        jmp     eint0                   ; too many nested interrupts   76711
meg4:   mov     dx,[di].pi_address
        add     dx,ESP_R_SID
        in      al,dx
        mov     [di].pi_sid,al                  ; (sid) = SID;

mov     bl,sid_buf_index
xor     bh,bh
mov     sid_buf[bx],al
inc     sid_buf_index

        cmp     al,0

        je      eint000                         ; if SID is zero, not my interrupt

        ; Was interrupt caused by DMA Timeout condition? Then clear condition
        ; and ignore
        test    [di].pi_sid,ESP_SID_DMATO
        jz      eint5
        mov     dx,[di].pi_address
eint3:  in      al,dx
        test    al,ESP_RDY_CMD1
        jz      eint3
        add     dx,ESP_R_CMD1-ESP_R_RDY
        mov     al,ESP_CMD_CLEARDMAREQ
        out     dx,al

        ; Check next for DMA Transfer Finished
eint5:  test    [di].pi_sid,ESP_SID_DMAFIN
        jz      eint6

        ;      to get around firmware problem: ESP may generate DMA Transfer
        ; interrupts when in Programmed I/O mode.      fix is this: if no
        ; DMA transfer was started, then clear DMA condition and ignore.
        cmp     [di].pi_DMA_outstanding,0
        jne     eint5a

        ; Ignore this DMA Finished condition.
        add     dx,ESP_R_RDY-ESP_R_SID
hack1:  in      al,dx
        test    al,ESP_RDY_CMD1
        jz      hack1
        add     dx,ESP_R_CMD1-ESP_R_RDY
        mov     al,ESP_CMD_CLEARDMAREQ
        out     dx,al
        ; WAIT FOR DMAFIN BIT IN SID TO CLEAR!
        add     dx,ESP_R_SID-ESP_R_CMD1
hack2:   in      al,dx
        test    al,ESP_SID_DMAFIN
        jnz     hack2
        jmp     SHORT eint6

eint5a:
        ; One of the ports DID start a DMA transfer, and now it's finished
        mov     si,[di].pi_DMA_outstanding
        ; Was last DMA transfer a WRITE TO memory or a READ FROM memory
eint5b: test    [di].pi_flagx,PIF_DMA_READ
        jnz     eint5c
        call    DMARxInt                        ; DMA_WRITE - Receive finished
        jmp     SHORT eint6

eint5c: call    DMATxInt                        ; DMA_READ - Transmit finished

        ; Check for error on either port
eint6:  test    [di].pi_sid,ESP_SID_ERROR1 OR ESP_SID_ERROR2
        jz      eint7
        mov     si,[di].pi_port1
        test    [di].pi_sid,ESP_SID_ERROR1
        jnz     eint6a
        mov     si,[di].pi_port2
eint6a:
        call    ErrInt

        ; Now process any Rx or Tx ints in round-robin order.
eint7:  mov     cx,MAX_RR_INDEX
eint7a: mov     bl,[di].pi_rrindex
        xor     bh,bh
        mov     al,RRMaskTable[bx]
        test    al,[di].pi_sid
        jz      eint8                           ; this bit not set, test next

        ; Service the interrupt corresponding to the RRMaskTable entry.
        ; Use pi_rrindex once again to find address of proper subroutine.
        shl     bl,1                            ; adjust for word pointer
        SaveReg         <cx>
        call    RRServiceTable[bx]
        RestoreReg      <cx>

        ; Check flags set by subroutine if it encountered a problem.
        test    [di].pi_flagx,PIF_CI_NULL
        ljnz    eint000                         ; ComInfo ptr null, abandon

        test    [di].pi_flagx,PIF_NOT_OPEN
        ljnz    eint00                          ; Port not open, abandon

        ; Increment pi_rrindex, wrap to zero when it gets to MAX_RR_INDEX.
        ; pi_rrindex now points to mask used to test pi_sid next time around.
eint8:  inc     [di].pi_rrindex
        cmp     [di].pi_rrindex,MAX_RR_INDEX
        jne     eint9                           ; didn't wrap
        mov     [di].pi_rrindex,0               ; wrapped, reset to zero

eint9:
        ; If we haven't looked at all the RRMaskTable entries yet, test
        ; pi_sid again with new mask.
        loop    eint7a

eint10:
        CLI

        ; done servicing the actual interrupt(s), tell kernel to reset 8259 for me
        mov     di,[si].ci_pinfo
  ;     mov     al,[di].pi_irq          ; irq                     remove 76711
  ;     mov     dl,DevHlp_EOI                 ; end of interrupt  remove 76711
  ;     DevHelp                                                   remove 76711
        DevEOI    <byte ptr [di].pi_irq>,DevHlp  ;                add    76711

        cmp     Kernel_Type,ABIOS_COM
        je      eint70

        ; Because IRQs on AT-bus are edge-triggered, SID mask must be set to
        ; zero. This will lower the IRQ line, allowing another edge to occur,
        ; should an unserviced IRQ be pending when the "real" SID mask is
        ; programmed. This is unnecessary if a DMA transfer was started
        ; during this ISR, since DMA operations on the ESP always disable the
        ; IRQ for the duration of the transfer.
        test    [di].pi_flagx,PIF_RESET_MASK
        jz      eint70

        mov     dx,[si].ci_esp_address          ; (dx) -> ready reg
eint90: in      al,dx                            ; wait for cmd1 ready
        test    al,ESP_RDY_CMD1
        jz      eint90

        mov     al,ESP_CMD_SETSVCMASK
        add     al,[si].ci_cmd_offset
        add     dx,ESP_R_CMD1-ESP_R_RDY         ; (dx) -> cmd1 reg
        out     dx,al                           ; out cmd1

        add     dx,ESP_R_RDY-ESP_R_CMD1         ; (dx) -> ready reg
eint92: in      al,dx
        test    al,ESP_RDY_CMD2                ; wait for cmd2 ready
        jz      eint92

        add     dx,ESP_R_CMD2-ESP_R_RDY         ; (dx) -> cmd2 reg
        mov     al,0
        out     dx,al                           ; out next data byte

        mov     dx,[si].ci_esp_address          ; (dx) -> ready reg
eint95: in      al,dx                            ; wait for cmd1 ready
        test    al,ESP_RDY_CMD1
        jz      eint95

        mov     al,ESP_CMD_SETSVCMASK
        add     al,[si].ci_cmd_offset
        add     dx,ESP_R_CMD1-ESP_R_RDY         ; (dx) -> cmd1 reg
        out     dx,al                           ; out cmd1

        add     dx,ESP_R_RDY-ESP_R_CMD1         ; (dx) -> ready reg
eint97: in      al,dx
        test    al,ESP_RDY_CMD2                ; wait for cmd2 ready
        jz      eint97

        add     dx,ESP_R_CMD2-ESP_R_RDY         ; (dx) -> cmd2 reg
        mov     di,[si].ci_pinfo
        mov     al,[di].pi_svcmask
        out     dx,al                           ; out next data byte

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

eint80: CALLFAR UnLinkHeadRP    ; (es:di) -> runnable request packet
        jc      eint85          ; no more to run
        CALLFAR ProcRun
        jmp     SHORT eint80    ; try to run another

eint85: RestoreReg      <si>    ; (ds:si) -> cominfo
        mov     di,[si].ci_pinfo
        ChkComInfoPtr

eint99: ; exiting first level interrupt
        and     [si].ci_flagx,NOT FX_DATA_MOVED ; clear data moved flag

eintx:  mov     sp,bp           ; restore stack frame
        RestoreReg      <di>    ; (ds:di) -> ESPInfo
        .errnz  D_BAD
eintxx:
        dec     [di].pi_depth
        clc                     ; clear carry to show it was our interrupt
        ret

eintnestx:
        or      [di].pi_flagx,PIF_INT_NESTED     ; flag nested int occurred
        jmp     SHORT eintx                     ; done

;* einterr - exit point for internal error at interrupt time
;
;       isuue EOI and return
;       Interrupt level has already been released (UnSetIRQ) and
;       16450 interrupt disabled in ShutdownPort,
;       but we still need to issue the EOI for this interrupt.
;
;       ENTRY   (ds) -> DD Data Seg

Entry   einterr,,,nocheck

        mov     sp,bp           ; restore stack frame
        pop     di              ; (ds:si) -> EspInfo
        ChkComInfoPtr
 ;      mov     al,[di].pi_irq                                    remove 76711
 ;      mov     dl,DevHlp_EOI           ; end of interrupt        remove 76711
 ;      DevHelp                                                   remove 76711
        DevEOI    <byte ptr [di].pi_irq>,DevHlp  ;                add    76711
        ; can't get nested ints because 16450 chip interrupts are disabled
        jmp     SHORT eintxx

EndProc EspInt


;**     EMxInt - Modem Status Interrupt (lowest priority)
;
;       ENTRY   (ds:si) -> ComInfo
;               (al) = modem status register
;
;       EXIT    none
;
;       USES    ax dx

Procedure EMxInt,NEAR
        ASSUME cs:E_CSEG,ds:DSEG,es:NOTHING,ss:NOTHING

        ChkComInfoPtr


        SaveReg         <ax>

        ; If DSR sensitivity is enabled, look for changes in DSR
        test    [si].ci_dcb_flags1,F1_IN_DSR_SENSE
        jz      mx30

        test    al,MS_DSR
        jnz     mx20                            ; DSR went high

        ; DSR went low, so clear FX_IN_DSR_OK to let RxInt routine know to
        ; ignore RxFIFO ints. But may be some good data already in ESP's FIFO,
        ; so do GetRxBytes to find out how much and remember in ci_qty_good.
        CLR     [si].ci_flagx,FX_IN_DSR_OK      ; DSR is NOT OK.
        GetRxBytes                              ; (al) = MSB; (ah) = LSB
        xchg    ah,al                           ; (ax) = bytes in ESP's FIFO
        mov     [si].ci_qty_good,ax
        jmp     SHORT mxix

        ; DSR went high, so set FX_IN_DSR_OK to let RxInt rountine know data
        ; is good from now on. Flush ESP's FIFO to dump old (bad) data.
mx20:
        FlushRxFIFO                             ; dump old data
        SET     [si].ci_flagx,FX_IN_DSR_OK

        ; If DCD ouput handshaking is enabled, look for change in DCD.
mx30:   test    [si].ci_dcb_flags1,F1_OUT_DCD_FLOW
        jz      mxix

        test    [si].ci_msrshadow,MS_DCD
        jnz     mx40

        ; DCD went low, so XOFF local transmitter
        mov     ah,ESP_CMD_XOFFLOCAL
        jmp     SHORT mx50

        ; DCD went low, so XON local transmitter
mx40:   mov     ah,ESP_CMD_XONLOCAL

mx50:   mov     dx,[si].ci_esp_address
mx55:   in      al,dx
        test    al,ESP_RDY_CMD1
        jz      mx55
        add     dx,ESP_R_CMD1-ESP_R_RDY     ; (dx) -> cmd1 reg
        mov     al,ah
        add     al,[si].ci_cmd_offset
        out     dx,al

mxix:   RestoreReg      <ax>

        ; Since ESP won't let us look at delta bits in MSR, must determine
        ; which signals have changed by comparing new MSR to msrshadow.
        mov     ah,[si].ci_msrshadow    ; (ah) = old MSR, (al) = new MSR
        mov     bx,ax                   ; (bh) = old MSR, (al) = new MSR
        xor     ah,al                   ; (ah) = delta MSR, (al) = new MSR
        CLR     ah,MS_DELTAS            ; real deltas are useless
        mov     [si].ci_msrshadow,al    ; shadow contents of new MSR

        ; Create the event mask from the modem status deltas
                        ; y = our delta carrier detect
                        ; t = trailing edge ring indicator
                        ; d = our delta data set ready
                        ; c = our clear to send
                        ;      AH                    AL            C
        mov     al,ah   ; y . d c . t . .       y . d c . t . .
        shl     ax,1    ; . d c . t . . y       . d c . t . . .
        shl     al,1    ; . d c . t . . y       d c . t . . . .
        shr     ah,1    ; . . d c . t . .       d c . t . . . .    y
        rcr     al,1    ; . . d c . t . .       y d c . t . . .
        shr     ax,2    ; . . . . d c . t       . . y d c . t .
        ror     ah,1    ; t . . . . d c .       . . y d c . t .
        shr     ah,7    ; . . . . . . . t       . . y d c . t .
        shr     al,2    ; . . . . . . . t       . . . . y d c .
        shl     al,2    ; . . . . . . . t       . . y d c . . .

        and     ax,EV_CTS OR EV_DSR OR EV_DCD OR EV_Ring
        or      [si].ci_event,ax
        ret

EndProc EMxInt

;**     Rx1Int/Rx2Int - Receive FIFO Interrupt
;
;       ENTRY   (ds:di) -> ESPInfo structure
;
;       EXIT
;
;       USES    ax bx cx dx si

Procedure Rx1Int,NEAR
        ASSUME cs:E_CSEG,ds:DSEG,es:NOTHING,ss:NOTHING
        mov     si,[di].pi_port1
        jmp     SHORT rf10

Entry Rx2Int
        mov     si,[di].pi_port2

rf10:   or      si,si
        jnz     rf20
        SET     [di].pi_flagx,PIF_CI_NULL       ; remember ComInfo ptr is null
        jmp     rfix

rf20:   cmp     [si].ci_nopens,0
        jne     rf30
        SET     [di].pi_flagx,PIF_NOT_OPEN      ; remember port not open
        jmp     rfix

rf30:   test    [si].ci_flagx,FX_LAST_CLOSE
        ljnz    rfix

        ; Don't waste any more time if this will be a DMA transfer but
        ; a DMA transfer is already in progress. In this case, ignore this
        ; RxFIFO int, and Esp will give us another one next time.
        mov     di,[si].ci_pinfo
        SET     [di].pi_flagx,PIF_RESET_MASK            ; assume no DMA
        cmp     [si].ci_rx_request,DMA_REQ_DISABLE
        je      rf35
        cmp     [di].pi_DMA_outstanding,0
        ljne    rfix

        ; Handling DSR sensitivity is tricky. If this is first RxInt since
        ; DSR was dropped, may still have good data in ESP's FIFO. ci_qty_good
        ; will be non-zero in this case, and exactly that many bytes should be
        ; transferred to in que -- remember to reset ci_qty_good to zero.
        ; If ci_qty_good is zero (good data already processed), and
        ; FX_IN_DSR_OK is NOT SET, then ignore this interrupt.
rf35:
        cmp     [si].ci_qty_good,0
        jne     rf40                            ; transfer data already in FIFO

        test    [si].ci_flagx,FX_IN_DSR_OK
        jnz     rf40

        ; If we ignore this interrupt, we must reset the service mask before
        ; leaving the isr.
        jmp     rfix

;       calculate length of transfer
;       there are two cases:
;       1. in ptr is < out ptr
;               in this case, we can only put (out - in) - 1 bytes in
;       2. in ptr >= out ptr
;               In this case, we can only xfer to end of buffer.
;               If in == out == base, we must leave last byte
;               empty.
rf40:   mov     cx,[si].ci_qin.ioq_in
        mov     dx,[si].ci_qin.ioq_out
        sub     cx,dx                   ; (cx) = -(out - in)
        jb      rf50                    ; in < out, can only go to (out - 1)
        add     cx,dx                   ; (cx) = ioq_in
        sub     cx,[si].ci_qin.ioq_end
        cmp     dx,[si].ci_qin.ioq_base
        jne     rf60

rf50:   inc     cx                      ; 'subtract' from negated count

rf60:   neg     cx                      ; (cx) = transfer count
        jcxz    rf70                    ; no room, return
        jmp     SHORT go

        ; No more room in input queue until consumer thread removes some data,
        ; so tell ESP we don't want to hear about receive data
rf70:
        call    TurnOffESP
        CLR     [di].pi_flagx,PIF_RESET_MASK    ; TurnOffESP already reset svc mask
        jmp     rfix

        ; (cx) = room left in qin.
        ; Handle ridiculous DSR sensitivity first.
go:     cmp     [si].ci_qty_good,0
        je      rf120
        sub     [si].ci_qty_good,cx
        jg      rf130                     ; not room for all
        mov     cx,[si].ci_qty_good     ; transfer all "good" data
        mov     [si].ci_qty_good,0
        jmp     SHORT rf130

rf120:
        ; (cx) = room left in qin.
        SaveReg         <cx>
        GetRxBytes                             ; (ax) = bytes ready in ESP
        RestoreReg      <cx>
        xchg    ah,al                   ; status2 is really MSB
        or      ax,ax                   ; does ESP have any bytes to transfer
        jz      rfix                    ; no, false alram

        cmp     cx,ax
        jbe     rf130                   ; not enough room, just fill up queue
        mov     cx,ax                   ; enough room, transfer all ESP has

rf130:
        ; (cx) = bytes to transfer
        ; Are we doing DMA or PIO?
        cmp     [si].ci_rx_request,DMA_REQ_DISABLE
        jne     rf135

        ; Do that PIO
        call    RxPIO
        jmp     rfix

rf135:
        ; Set up DMA controller for WRITE transfer
        mov     [di].pi_xfer_count,cx
        cmp     Kernel_Type,ABIOS_COM
        je      rf140
        mov     al,AT_DMA_MODE_WRITE
        mov     ah,[di].pi_DMA_chan
        call    ATsetupDMA
        jmp     SHORT rf145

rf140:  mov     bl,ABIOS_FUNC_WRITEMEM
        call    MCAbeginDMA

        ; DMAC ready, tell ESP to start
rf145:  mov     [di].pi_DMA_outstanding,si          ; save ComInfo ptr
mov bl,sid_buf_index
dec bl
xor bh,bh
mov dx,si
mov dma_buf[bx],dl
        mov     dx,[si].ci_esp_address          ; (dx) -> ready reg
rf150:  in      al,dx
        test    al,ESP_RDY_CMD1
        jz      rf150
        add     dx,ESP_R_CMD1-ESP_R_RDY
        mov     al,ESP_CMD_DMARX
        add     al,[si].ci_cmd_offset
        out     dx,al
        add     dx,ESP_R_RDY-ESP_R_CMD1
rf160:  in      al,dx
        test    al,ESP_RDY_STATUS1
        jz      rf160
        add     dx,ESP_R_STATUS1-ESP_R_RDY
        in      al,dx

        ; Driver got data from the hardware, so reset timeout counter
        mov     cx,[si].ci_r_to_start
        mov     [si].ci_r_to,cx

        ; Set flag in my pinfo to let everyone know ESP is busy doing DMA
        SET     [di].pi_flagx,PIF_PECAN_BUSY
        CLR     [di].pi_flagx,PIF_DMA_READ      ; remember DMA was a Write
        CLR     [di].pi_flagx,PIF_RESET_MASK    ; started DMA, no reset needed
rfix:   ret

EndProc Rx1Int

;**     Tx1Int/Tx2Int - Transmit FIFO Interrupt
;
;       ENTRY   (ds:di) -> ESPInfo
;
;       EXIT
;
;       USES    ax bx cx dx si

Procedure Tx1Int,NEAR
        ASSUME cs:E_CSEG,ds:DSEG,es:NOTHING,ss:NOTHING
        mov     si,[di].pi_port1
        jmp     SHORT tx000

Entry Tx2Int
        mov     si,[di].pi_port2

tx000:  or      si,si
        jnz     tf00
        SET     [di].pi_flagx,PIF_CI_NULL       ; remember ComInfo ptr is null
        jmp     tfix

tf00:   cmp     [si].ci_nopens,0
        jne     tf10
        SET     [di].pi_flagx,PIF_NOT_OPEN      ; remember port not open
        jmp     tfix

tf10:
        ; Don't waste any more time if this will be a DMA transfer but
        ; a DMA transfer is already in progress. In this case, ignore this
        ; TxFIFO int, and Esp will give us another one next time.
        mov     di,[si].ci_pinfo
        SET     [di].pi_flagx,PIF_RESET_MASK            ; assume no DMA
        cmp     [si].ci_tx_request,DMA_REQ_DISABLE
        je      tf15
        cmp     [di].pi_DMA_outstanding,0
        ljne    tfix

tf15:
        test    [si].ci_flagx,FX_WAITING_TX_EMPTY
        ljz      tf20

        ; FX_WAITING_EMPTY, which means this is the TxFIFO interrupt
        ; that tells us all data was sent, and it's time to drop RTS
        CLR     [si].ci_flagx,FX_WAITING_TX_EMPTY
        CLR     [si].ci_esp_flow1,ESP_FC1_RX_RTS        ; RTS not used for flow
        CLR     [si].ci_esp_flow1,ESP_FC1_ON_RTS        ; drop RTS
        mov     ah,[si].ci_esp_flow1
        mov     al,[si].ci_esp_flow2
        SetFCType       ah,al

        ; Remember to reset TxFIFO trigger level to original value!
        call    ResetTriggerLevel

        ; We needed this TxFIFO int to tell us when to drop RTS, but if
        ; there's no more data to send, disable TxFIFO int
        call    TriggerTX

tf20:   cmp     [si].ci_w_rp._hi,0              ; really OK to transmit?
        lje     tfix                            ; no, get out

        ; check for RTS toggling
        mov     al,[si].ci_dcb_flags2
        and     al,F2_RTS_MASK
        cmp     al,F2_RTS_TOGGLE
        ljne    tf23

        test    [si].ci_flagx,FX_WAITING_TX_EMPTY
        ljnz    tf23                    ; took care of this condition above

        ; Raise RTS before sending tx data to ESP, and reset TxFIFO trigger
        ; to "FIFO empty", so next TxFIFO int will be when ESP has transmitted
        ; all the data
        SET     [si].ci_flagx,FX_WAITING_TX_EMPTY
        mov     ax,[si].ci_rx_trigger
        SetTriggers     ah,al,HIGH(ESP_TRIGGER_TX_EMPTY),LOW(ESP_TRIGGER_TX_EMPTY)

        ; Don't raise RTS if transmitter is flowed off
        test    [si].ci_errshadow2,ESP_ERR2_OFFLOCAL
        jnz     tf23
        CLR     [si].ci_esp_flow1,ESP_FC1_RX_RTS        ; RTS not used for flow
        SET     [si].ci_esp_flow1,ESP_FC1_ON_RTS        ; raise RTS
        mov     ah,[si].ci_esp_flow1
        mov     al,[si].ci_esp_flow2
        SetFCType       ah,al

tf23:   mov     ax,[si].ci_qout.ioq_in          ; (ax) = in
        mov     bx,[si].ci_qout.ioq_out         ; (bx) = out
        cmp     ax,bx
        jne     tf25
        jmp     tfc                              ; no data in queue

tf25:    jb      tf30
; in > out
        sub     ax,bx                           ; (ax) = in - out
        jmp     SHORT tf40

; in < out
tf30:    mov     ax,[si].ci_qout.ioq_end         ; (ax) = end
        sub     ax,bx                           ; (ax) = end - out

        ; Number of bytes we'd like to transfer is in (ax).
        ; Let's see if ESP has room for all of them.
tf40:   mov     bx,ESP_DEF_TRIGGER_TX
        cmp     ax,bx
        jbe     tf50                             ; plenty of room
        mov     ax,bx                           ; can't transfer all of them

tf50:
        ; (ax) = bytes to transfer
        ; Are we doing DMA or PIO?
        cmp     [si].ci_tx_request,DMA_REQ_DISABLE
        jne     tf55

        ; Transfer bytes via PIO from out que to ESP's Tx register.
        mov     cx,ax                           ; loop counter
        cmp     [si].ci_cmd_offset,0
        jne     tp10
        mov     bl,ESP_RDY_TX1
        mov     bh,ESP_R_TX1-ESP_R_RDY
        jmp     SHORT tp5
tp10:   mov     bl,ESP_RDY_TX2
        mov     bh,ESP_R_TX2-ESP_R_RDY

        ; (bh) = offset of proper tx register; (bl) = ready register mask
tp5:    push    bx                              ; ReadQueueByte uses bx
        CALLFAR ReadQueueByte                   ; (al) = byte to tx
        pop     bx
        jc      tp40
tp20:   mov     ah,al                           ; (ah) = byte to tx
        mov     dx,[si].ci_esp_address
tp30:   in      al,dx
        test    al,bl                           ; is tx register ready?
        jz      tp30
        add     dl,bh                           ; (dx) = tx register
        mov     al,ah                           ; (al) = byte to tx
        out     dx,al
        loop    tp5                             ; finished with all bytes yet?

        ; ESP is full or driver's out que is empty
tp40:   cmp     [si].ci_qout.ioq_count,0
        jne     tp45
        SET     [si].ci_event,EV_TX_EMPTY       ; show that txq emptied

tp45:   call    FinishWriteReq
        jmp     SHORT tfix

tf55:
        ; Set up DMA controller for READ transfer
        mov     [di].pi_xfer_count,ax
        cmp     Kernel_Type,ABIOS_COM
        je      tf60
        mov     al,AT_DMA_MODE_READ
        mov     ah,[di].pi_dma_chan
        call    ATsetupDMA
        jmp     SHORT tf75

tf60:   mov     bl,ABIOS_FUNC_READMEM
        call    MCAbeginDMA

        ; DMAC ready, tell ESP to start
tf75:   mov     [di].pi_DMA_outstanding,si          ; save ComInfo ptr
mov bl,sid_buf_index
dec bl
xor bh,bh
mov dx,si
mov dma_buf[bx],dl
        mov     dx,[si].ci_esp_address          ; (dx) -> ready reg
tf80:   in      al,dx
        test    al,ESP_RDY_CMD1
        jz      tf80
        add     dx,ESP_R_CMD1-ESP_R_RDY         ; (dx) -> cmd1 reg
        mov     al,ESP_CMD_DMATX
        add     al,[si].ci_cmd_offset
        out     dx,al
        add     dx,ESP_R_RDY-ESP_R_CMD1         ; (dx) -> ready reg
tf90:   in      al,dx
        test    al,ESP_RDY_STATUS1
        jz      tf90
        add     dx,ESP_R_CMD1-ESP_R_RDY         ; (dx) -> ready reg
        in      al,dx                           ; read status1

        ; Driver gave the data to the hardware, so reset timeout counter
        mov     cx,[si].ci_w_to_start
        mov     [si].ci_w_to,cx

        ; Set flag in my pinfo to let everyone know ESP is busy doing DMA
        SET     [di].pi_flagx,PIF_PECAN_BUSY
        SET     [di].pi_flagx,PIF_DMA_READ     ; remember DMA was a Read
        CLR     [di].pi_flagx,PIF_RESET_MASK
tfc:    mov     di,[si].ci_pinfo                ; restore ESPInfo ptr
tfix:   ret

EndProc Tx1Int

;**     DMARxInt - DMA Finished Interrupt
;
;       ENTRY   (ds:si) -> ComInfo
;
;       EXIT
;
;       USES    ax bx cx dx si

Procedure DMARxInt,NEAR
        ASSUME cs:E_CSEG,ds:DSEG,es:NOTHING,ss:NOTHING

        ; ESP finished with DMA Receive, so adjust in ptr for inque.
        cmp     Kernel_Type,ABIOS_COM
        jne     drx
        call    MCAendDMA

drx:    mov     [di].pi_DMA_outstanding,0
        test    [si].ci_dcb_flags2,F2_NULL_STRIP
        jz      drx0                            ; don't strip nulls

        call    StripNulls                      ; will save/restore regs

drx0:   mov     bx,[si].ci_qin.ioq_in           ; (bx) -> original in ptr
        mov     ax,[di].pi_xfer_count           ; (ax) -> bytes added to in que
        add     bx,ax                           ; (bx) -> updated in ptr

        cmp     bx,[si].ci_qin.ioq_end          ; check for wrap around
        jb      drx1                             ; did not wrap

        mov     bx,[si].ci_qin.ioq_base         ; wrap

        ; check for overflow condition
drx1:    cmp     bx,[si].ci_qin.ioq_out
        je      drx2                             ; yes, overflow

        or      [si].ci_event,EV_RX_CHAR        ; no overflow, mark event
        mov     [si].ci_qin.ioq_in,bx           ; save away updated in ptr
        add     [si].ci_qin.ioq_count,ax        ; update count
        jmp     SHORT drx4


drx2:   mov     [si].ci_rx_state,RX_STATE_HOLDING

drx4:   call    FinishReadReq

        ; issue Clear DMA Request command
drx9:   mov     dx,[si].ci_esp_address          ; (dx) -> ready reg
drx10:  in      al,dx
        test    al,ESP_RDY_CMD1
        jz      drx10
        add     dx,ESP_R_CMD1-ESP_R_RDY
        mov     al,ESP_CMD_CLEARDMAREQ
        out     dx,al
        ; WAIT FOR DMAFIN BIT IN SID TO CLEAR!
        add     dx,ESP_R_SID-ESP_R_CMD1
drx11:  in      al,dx
        test    al,ESP_SID_DMAFIN
        jnz     drx11

        mov     di,[si].ci_pinfo
        CLR     [di].pi_flagx,PIF_PECAN_BUSY    ; tell folks ESP is free
        SET     [di].pi_flagx,PIF_RESET_MASK

drix:   ret

EndProc DMARxInt

Procedure DMATxInt,NEAR
        ASSUME cs:E_CSEG,ds:DSEG,es:NOTHING,ss:NOTHING

        ; ESP finished with DMA Receive, so adjust out ptr for outque.
        cmp     Kernel_Type,ABIOS_COM
        jne     dt5
        call    MCAendDMA

dt5:    mov     ax,[di].pi_xfer_count
        sub     [si].ci_qout.ioq_count,ax       ; adjust que count
        jnz     dt10                            ; didn't take last char
        SET     [si].ci_event,EV_TX_EMPTY       ; show that txq emptied

dt10:   add     [si].ci_qout.ioq_out,ax
        mov     ax,[si].ci_qout.ioq_out
        cmp     ax,[si].ci_qout.ioq_end         ; adjust for wrap if necessary
        jne     dt30                            ; did not wrap
        mov     ax,[si].ci_qout.ioq_base        ; wrap
        mov     [si].ci_qout.ioq_out,ax

dt30:   mov     [di].pi_DMA_outstanding,0
        call    FinishWriteReq

        ; issue Clear DMA Request command
dt7:    mov     dx,[si].ci_esp_address          ; (dx) -> ready reg
dt7a:   in      al,dx
        test    al,ESP_RDY_CMD1
        jz      dt7a
        add     dx,ESP_R_CMD1-ESP_R_RDY
        mov     al,ESP_CMD_CLEARDMAREQ
        out     dx,al
        ; WAIT FOR DMAFIN BIT IN SID TO CLEAR!
        add     dx,ESP_R_SID-ESP_R_CMD1
dt8:    in      al,dx
        test    al,ESP_SID_DMAFIN
        jnz     dt8

        mov     di,[si].ci_pinfo
        CLR     [di].pi_flagx,PIF_PECAN_BUSY    ; tell folks ESP is free
        SET     [di].pi_flagx,PIF_RESET_MASK
        ret

EndProc DMATxInt

Procedure ErrInt,NEAR
        ASSUME cs:E_CSEG,ds:DSEG,es:NOTHING,ss:NOTHING

        SET     [di].pi_flagx,PIF_RESET_MASK
        CLR     [di].pi_flagx,PIF_PECAN_BUSY

; GET ERROR STATUS FROM ESP
        call    GetError                        ; fills in errshadow1 and errshadow2

; TEST FOR BREAK/LINE STATUS FIRST
        test    [si].ci_errshadow1,ESP_ERR1_BRK OR ESP_ERR1_FRAME OR ESP_ERR1_PARITY
        jz      er35

        call    LineStatusError
; TEST FOR RX TIMEOUT NEXT
er35:   test    [si].ci_errshadow1,ESP_ERR1_RXTO
        ljz      er90

        ; Don't wait on RxFIFO trigger level, transfer bytes waiting now.
        ; Don't waste any more time if this will be a DMA transfer but
        ; a DMA transfer is already in progress. In this case, ignore this
        ; int, and Esp will give us another one next time.
        cmp     [si].ci_rx_request,DMA_REQ_DISABLE
        je      er36
        cmp     [di].pi_DMA_outstanding,0
        ljne     er90                           ; process next error condition


        ; Handling DSR sensitivity is tricky. If this is first Rx Timeout since
        ; DSR was dropped, may still have good data in ESP's FIFO. ci_qty_good
        ; will be non-zero in this case, and exactly that many bytes should be
        ; transferred to in que -- remember to reset ci_qty_good to zero.
        ; If ci_qty_good is zero (good data already processed), and
        ; FX_IN_DSR_OK is NOT SET, then ignore this interrupt.
er36:
        cmp     [si].ci_qty_good,0
        jne     er37                            ; transfer data already in FIFO

        test    [si].ci_flagx,FX_IN_DSR_OK
        jnz     er37

        ; Calculate length of transfer -- there are two cases
        ; 1. in ptr is < out ptr
        ;    in this case, we can only put (out - in) - 1 bytes in
        ; 2. in ptr >= out ptr
        ;    In this case, we can only xfer to end of buffer.
        ; If in == out == base, we must leave last byte empty
er37:   mov     cx,[si].ci_qin.ioq_in
        mov     dx,[si].ci_qin.ioq_out
        sub     cx,dx                   ; (cx) = -(out - in)
        jb      er40                    ; in < out, can only go to (out - 1)
        add     cx,dx                   ; (cx) = ioq_in
        sub     cx,[si].ci_qin.ioq_end
        cmp     dx,[si].ci_qin.ioq_base
        jne     er50

er40:   inc     cx                      ; 'subtract' from negated count

er50:   neg     cx                      ; (cx) = transfer count
        jc      er53                    ; carry set, then (cx) not zero

        jmp     er90                    ; no room in que for rx data

        ; (cx) = room left in qin
        ; Handle ridiculous DSR sensitivity first
er53:   cmp     [si].ci_qty_good,0
        je      er57

        sub     [si].ci_qty_good,cx
        jg      er60
        mov     cx,[si].ci_qty_good
        mov     [si].ci_qty_good,0
        jmp     SHORT er60

er57:   SaveReg         <cx>
        GetRxBytes                             ; (ax) = bytes ready in ESP
        RestoreReg      <cx>
        xchg    al,ah
        cmp     [si].ci_rx_request,DMA_REQ_DISABLE
        je      er58                            ; don't test for RxBytes=0
        cmp     ax,0                            ; no bytes available
        lje     er90

er58:   cmp     cx,ax
        ja      er60                            ; There's room for all
        mov     ax,cx                           ; not enough room
er60:
        ; (ax) = bytes to transfer
        ; Are we doing DMA or PIO?
        cmp     [si].ci_rx_request,DMA_REQ_DISABLE
        jne     er62

        ; Do that PIO
        mov     cx,ax                           ; RxPIO expects count in CX
        call    RxPIO
        jmp     er85

er62:
        ; Set up DMA controller for WRITE transfer
        mov     [di].pi_xfer_count,ax
        cmp     Kernel_Type,ABIOS_COM
        je      er65
        mov     al,AT_DMA_MODE_WRITE
        mov     ah,[di].pi_dma_chan
        call    ATsetupDMA
        jmp     SHORT er69

er65:   mov     bl,ABIOS_FUNC_WRITEMEM
        call    MCAbeginDMA

        ; DMAC ready, tell ESP to start
er69:   mov     [di].pi_DMA_outstanding,si          ; save ComInfo ptr
mov bl,sid_buf_index
dec bl
xor bh,bh
mov dx,si
mov dma_buf[bx],dl
        mov     dx,[si].ci_esp_address          ; (dx) -> ready reg
er70:   in      al,dx
        test    al,ESP_RDY_CMD1
        jz      er70
        add     dx,ESP_R_CMD1-ESP_R_RDY         ; (dx) -> cmd1 reg
        mov     al,ESP_CMD_DMARX
        add     al,[si].ci_cmd_offset
        out     dx,al
        add     dx,ESP_R_RDY-ESP_R_CMD1         ; (dx) -> ready reg
er80:    in      al,dx
        test    al,ESP_RDY_STATUS1
        jz      er80
        add     dx,ESP_R_STATUS1-ESP_R_RDY      ; (dx) -> status1 reg
        in      al,dx

        ; Set flag in my pinfo to let everyone know ESP is busy doing DMA
        SET     [di].pi_flagx,PIF_PECAN_BUSY
        CLR     [di].pi_flagx,PIF_DMA_READ      ; remember DMA was a Write
        CLR     [di].pi_flagx,PIF_RESET_MASK

        ; Clear Tx and Rx bits in pi_sid -- only allow one DMA transfer per isr.
er85:   mov     al,ESP_SID_TXBOTH OR ESP_SID_RXBOTH
        not     al
        and     [di].pi_sid,al

; TEST FOR MSR
er90:
        test    [si].ci_errshadow1,ESP_ERR1_MSR
        jz      er130
        mov     dx,[si].ci_esp_address          ; (dx) -> ready reg
er100:  in      al,dx
        test    al,ESP_RDY_CMD1
        jz      er100
        add     dx,ESP_R_CMD1-ESP_R_RDY         ; (dx) -> cmd1 reg
        mov     al,ESP_CMD_GETUARTS
        add     al,[si].ci_cmd_offset
        out     dx,al
        add     dx,ESP_R_RDY-ESP_R_CMD1         ; (dx) -> ready reg
er110:  in      al,dx
        test    al,ESP_RDY_STATUS1
        jz      er110
        add     dx,ESP_R_STATUS1-ESP_R_RDY      ; (dx) -> status1
        in      al,dx                           ; (al) = LSR, throw away
        add     dx,ESP_R_RDY-ESP_R_STATUS1      ; (dx) -> ready reg
er120:   in      al,dx
        test    al,ESP_RDY_STATUS2
        jz      er120
        add     dx,ESP_R_STATUS2-ESP_R_RDY      ; (dx) -> status2 reg
        in      al,dx                           ; (al) = MSR reg
        call    EMxInt

; TEST FOR RX OVERFLOW
er130:  test    [si].ci_errshadow1,ESP_ERR1_RXOF
        jz      er240

        ; ignore ESP overruns if DSR sensitivity turned on
        test    [si].ci_dcb_flags1,F1_IN_DSR_SENSE
        jnz     er240
        ; DO WE FLAG AS OVERRUN, OR MAYBE "ESP prevents hw overruns"
        ;SET     [si].ci_comerr,CE_HW_OVERRUN

ifdef PERFVIEW
        ; If flagged as overrun, then update perfview counter    ;@PVW
        ;pvw_SW_Overrun              ;inc PerfView flag          ;@PVW
endif

; TEST FOR XON/XOFF
er240:  test    [si].ci_errshadow2,ESP_ERR2_OFFLOCAL
        jz      er250

        ; If we're waiting for transmitter to empty (RTS toggling) and get
        ; xoff'ed, then drop RTS. Raise it again when xon'ed.
        test    [si].ci_flagx,FX_WAITING_TX_EMPTY
        jz      er250
        CLR     [si].ci_esp_flow1,ESP_FC1_ON_RTS        ; drop RTS
        mov     ah,[si].ci_esp_flow1
        mov     al,[si].ci_esp_flow2
        SetFCType       ah,al

er250:  test    [si].ci_errshadow2,ESP_ERR2_ONLOCAL
        jz      erxx

        ; If we're waiting for transmitter to empty (RTS toggling) and get
        ; xon'ed, then raise RTS.
        test    [si].ci_flagx,FX_WAITING_TX_EMPTY
        jz      erxx
        SET     [si].ci_esp_flow1,ESP_FC1_ON_RTS        ; raise RTS
        mov     ah,[si].ci_esp_flow1
        mov     al,[si].ci_esp_flow2
        SetFCType       ah,al

erxx:   ret

EndProc ErrInt

Procedure StripNulls,NEAR
        ASSUME cs:E_CSEG,ds:DSEG,es:NOTHING,ss:NOTHING

        push    bp
        push    si
        push    di
        push    ds
        push    es

        mov     cx,[di].pi_xfer_count
        mov     di,WORD PTR [si].ci_qin.ioq_in  ;(ds:di) -> qin new data
        push    ds
        pop     es                              ;(es:di) -> qin new data
        ASSUME  ds:nothing
        ;Careful, can't access driver data segment until DS is restored from stack
        sub     bx,bx
        sub     ax,ax
scan:
        jcxz    thats_all                       ;(cx) = bytes left to scan
        repnz   scasb
        jnz     thats_all

        inc     bx                              ;(bx) = nulls removed
        mov     dx,cx                           ;(dx) = bytes left to scan
        mov     si,di                           ;(si) -> byte after null
        dec     di                              ;(di) -> null byte
        mov     bp,di                           ;(bp) -> null, and also new
                                                ;  data after "rep movsb"
        rep     movsb                           ;copy over null byte
        mov     di,bp                           ;(di) -> new bytes to scan
        mov     cx,dx                           ;(cx) = bytes left to scan
        jmp     scan

thats_all:
        pop     es
        pop     ds
        pop     di
        pop     si
        ASSUME  ds:DSEG
        sub     [di].pi_xfer_count,bx           ;(bx) = number of nulls removed
        pop     bp
        ret

EndProc StripNulls

;***    ATsetupDMA - Set the DMA channel up to do the I/O
;
;       ENTRY   AL = DMA mode
;               AH = DMA channel numbe
;
;       EXIT    AX is preserved
;
;       USES     BX, CX, DX, Flags

Procedure ATsetupDMA,NEAR
        ASSUME cs:E_CSEG,ds:DSEG,es:NOTHING,ss:NOTHING

        push    ax
        mov     bx,ax                   ; (bl) = DMA mode
        cli
        xchg    ah,al                   ; (ah) = DMA Mode
        or      al,4
        out     AT_DMA_MASK,al          ; set channel's mask bit
        IOWait
        out     AT_DMA_CLEAR,al         ; clear byte pointer F/F
        IOWait
        pop     ax                      ; restore channel and mode
        push    ax
        or      al,ah                   ; add channel number to command
        out     AT_DMA_MODE,al          ; Set DMA mode
        IOWait
        xor     dx,dx
        rol     ah,1
        add     dl,ah                   ; (dx) -> address register

        push    bx
        test    bl,AT_DMA_MODE_READ
        jz      at10                    ; mode == WRITE

        ; DMA mode == READ
        mov     cx,[si].ci_qout.ioq_phys._hi
        mov     [si].ci_qout.ioq_page,cx        ; (page) = hi byte of phys addr
        mov     ax,[si].ci_qout.ioq_out
        sub     ax,[si].ci_qout.ioq_base        ; (ax) = offset of start from base
        mov     cx,[si].ci_qout.ioq_phys._lo    ; (cx) = que base phys address
        add     ax,cx                           ; (ax) = start transfer address
        mov     bx,ax
        add     bx,[di].pi_xfer_count           ; (bx) = finish transfer address
        jmp     SHORT at20

        ; DMA mode == WRITE
at10:   mov     cx,[si].ci_qin.ioq_phys._hi
        mov     [si].ci_qin.ioq_page,cx         ; (page) = hi byte of phys addr
        mov     ax,[si].ci_qin.ioq_in
        sub     ax,[si].ci_qin.ioq_base         ; (ax) = offset of start from base
        mov     cx,[si].ci_qin.ioq_phys._lo     ; (cx) = que base phys address
        add     ax,cx                           ; (ax) = start transfer address
        mov     bx,ax
        add     bx,[di].pi_xfer_count           ; (bx) = finish transfer address

        ; If phys address of ioq_in/ioq_out plus transfer length is less than
        ; phys address of que base, que will wrap to next physical page.
        ; DMAC can't handle two different pages, so shorten transfer length,
        ; and do another transfer next time to get the rest.
        ; 
        ; (ax) = start transfer address, (bx) = finish transfer address
        ; (cx) = que base address
at20:   cmp    ax,cx
        jb     at25                             ; already on 2nd page

        cmp     bx,cx
        jae     at30                            ; no wrap

        ; Que would wrap, so calculate new shorter transfer length (go to
        ; page boundary)
        mov     bx,0
        sub     bx,ax                           ; (bx) = bytes to boundary
        ; (bx) = bytes to end of page boundary
        mov     [di].pi_xfer_count,bx
        jmp     SHORT at30

at25:   pop     bx
        test    bl,AT_DMA_MODE_READ
        jnz     at27
        inc     [si].ci_qin.ioq_page            ; begin transfer at 2nd page
        jmp     SHORT at35

at27:   inc     [si].ci_qout.ioq_page
        jmp     SHORT at35

at30:   pop     bx
at35:   out     dx,al                           ; Out low byte of address
        IOWait
        xchg    al,ah
        out     dx,al                           ; Out second byte of address
        IOWait
        inc     dx                              ; (dx) -> count register
        mov     ax,[di].pi_xfer_count           ; # bytes to transfer
        dec     ax
        out     dx,al                           ; Out lo byte of count
        IOWait
        xchg    al,ah
        out     dx,al                           ; Out hi byte of count
        IOWait
        pop     ax                              ; get back DMA channel number
        push    ax
        cmp     ah,1                            ; test channel is 1
        jne     at40                            ; no, channel is 3
        mov     dx,AT_DMA_PAGE_CH1
        jmp     SHORT at50

at40:   mov     dx,AT_DMA_PAGE_CH3
at50:   mov     ax,[si].ci_qout.ioq_page        ; assume mode is READ
        test    bl,AT_DMA_MODE_READ             ; test DMA mode is READ
        jnz     at60                            ; mode is READ, ax ready
        mov     ax,[si].ci_qin.ioq_page         ; mode is WRITE, set up ax
at60:   out     dx,al                           ; Out highest byte of address
        IOWait
        pop     ax
        push    ax
        xchg    ah,al
        out     AT_DMA_MASK,al                  ; enable DMAC for our channel
        pop     ax
        sti
        ret

Endproc ATsetupDMA

;***    MCAbeginDMA
;
;       ENTRY   BL = ABIOS function code for DMA READ or DMA WRITE
;
;       USES     BX, CX, DX, Flags
Procedure MCAbeginDMA,NEAR
        ASSUME cs:E_CSEG,ds:DSEG,es:NOTHING,ss:NOTHING

        SaveReg         <ds,es>
        SaveReg         <bx>

        ; Allocate ESP's arbitration level
        mov     di,[si].ci_pinfo
        mov     al,[di].pi_DMA_chan             ; actually DMA arb level
        setDS   HSEG
        mov     bx,alloc_ptr
        mov     [bx].arb,al
        mov     [bx].arbfunc,ABIOS_FUNC_ALLOCARB
        mov     [bx].arbrval,0FFFFh
        sub     ax,ax
        mov     WORD PTR [bx].arbres1,ax
        mov     WORD PTR [bx].arbres1+2,ax
        mov     [bx].arbres2,ax
        mov     WORD PTR [bx].arbres3,ax
        mov     WORD PTR [bx].arbres3+2,ax
        mov     [bx].arbres4,al

        xchg    si,bx
        ; (ds:si) -> ABIOS request block
        mov     dh,0                            ; use Start entry point
        mov     dl,DevHlp_ABIOSCommonEntry
        setES   DSEG
        call    es:[DevHlp]
        jc      alloc_failed
        cmp     [si].arbrval,0
        je      mca5
alloc_failed:
        mov     si,bx                           ; (si) -> ComInfo block
        ComErr  <MCABeginDMA: call to ABIOS_FUNC_ALLOC_ARB failed>,CALLFAR

mca5:
        setDS   DSEG
        xchg    si,bx                           ; (ds:si) -> ComInfo block
        RestoreReg      <bx>
        SaveReg         <bx>
        cmp     bl,ABIOS_FUNC_READMEM
        jne     mca10

        ; DMA Read from memory
        mov     cx,[si].ci_qout.ioq_phys._hi
        mov     [si].ci_qout.ioq_page,cx        ; (page) = hi byte of phys addr
        mov     ax,[si].ci_qout.ioq_out
        sub     ax,[si].ci_qout.ioq_base        ; (ax) = offset of start from base
        mov     cx,[si].ci_qout.ioq_phys._lo    ; (cx) = que base phys address
        add     ax,cx                           ; (ax) = start transfer address
        mov     bx,ax
        add     bx,[di].pi_xfer_count           ; (bx) = finish transfer address
        jmp     SHORT mca20

        ; DMA Write to memory
mca10:  mov     cx,[si].ci_qin.ioq_phys._hi
        mov     [si].ci_qin.ioq_page,cx         ; (page) = hi byte of phys addr
        mov     ax,[si].ci_qin.ioq_in
        sub     ax,[si].ci_qin.ioq_base         ; (ax) = offset of start from base
        mov     cx,[si].ci_qin.ioq_phys._lo     ; (cx) = que base phys address
        add     ax,cx                           ; (ax) = start transfer address
        mov     bx,ax
        add     bx,[di].pi_xfer_count           ; (bx) = finish transfer address

        ; If phys address of ioq_in/ioq_out plus transfer length is less than
        ; phys address of que base, que will wrap to next physical page.
        ; DMAC can't handle two different pages, so shorten transfer length,
        ; and do another transfer next time to get the rest.
        ; 
        ; (ax) = start transfer address, (bx) = finish transfer address
        ; (cx) = que base address
mca20:  cmp    ax,cx
        jb      mca25

        cmp     bx,cx
        jae     mca30

        ; Que would wrap, so calculate shorter transfer length
        ; (go to page boundardy)
        mov     bx,0
        sub     bx,ax
        ; (bx) = bytes to end of page boundary
        mov     [di].pi_xfer_count,bx
        jmp     SHORT mca30

mca25:  RestoreReg      <bx>                    ; (bx) = read or write
        cmp     bl,ABIOS_FUNC_READMEM
        je      mca27
        inc     [si].ci_qin.ioq_page
        jmp     SHORT mca35

mca27:  inc     [si].ci_qout.ioq_page
        jmp     SHORT mca35

mca30:  pop     bx

        ; (ax) = phys addr
mca35:  mov     cx,bx                           ; (cx) = read/write

        cmp     cl,ABIOS_FUNC_READMEM
        je      mca40
        mov     dx,[si].ci_qin.ioq_page
        jmp     mca45
mca40:
        mov     dx,[si].ci_qout.ioq_page

mca45:
        ; (ax) = phys address lo word
        ; (dx) = phys address high word
        ; (cl) = ABIOS_FUNC_READMEM or ABIOS_FUNC_WRITE_MEM
        ; Load as much as we can of ABIOS request block using registers.
        ; Loading from ESPInfo struc forces segment override since request
        ; block and ESPInfo are in different segments.
        setDS   HSEG
        setES   DSEG
        mov     bx,si                           ; (bx) -> ComInfo
        mov     si,read_write_ptr
        ; (ds:si) -> request block
        ; (es:di) -> ESPInfo
                      xor     ch,ch
        mov     [si].rwfunc,cx
        mov     [si].memaddr._lo,ax
        mov     [si].memaddr._hi,dx
        mov     ax,es:[di].pi_xfer_count
        dec     ax
        mov     [si].count._lo,ax
        mov     [si].count._hi,0
        mov     al,es:[di].pi_DMA_chan          ; actually DMA arb level
        mov     [si].rwarb,al
        mov     [si].arbrval,0FFFFh
        sub     ax,ax
        mov     WORD PTR [si].rwres1,ax
        mov     WORD PTR [si].rwres1+2,ax
        mov     [si].ioaddr._lo,ax
        mov     [si].ioaddr._hi,ax
        mov     [si].mode,al
        mov     [si].rwtc1,al
        mov     [si].rwtc2,al

        mov     dh,0                            ; use Start entry point
        mov     dl,DevHlp_ABIOSCommonEntry
        DevHelp
        jc      read_failed
        cmp     [si].arbrval,0
        je      mcaix
read_failed:
        mov     si,bx                           ; (si) -> ComInfo
        ComErr  <MCABeginDMA: call to ABIOS_FUNC_READ or WRITE failed>,CALLFAR

mcaix:  xchg    si,bx
        RestoreReg      <es,ds>
        ret

Endproc MCAbeginDMA

Procedure MCAendDMA,NEAR
        ASSUME cs:E_CSEG,ds:DSEG,es:NOTHING,ss:NOTHING

        SaveReg         <ds,es>
        mov     di,[si].ci_pinfo
        mov     al,[di].pi_DMA_chan             ; actually arb level
        setDS   HSEG
        mov     bx,disable_ptr
        ; Disable arbitration level
        mov     [bx].arb,al
        mov     [bx].arbfunc,ABIOS_FUNC_DISABLEARB
        mov     [bx].arbrval,0FFFFh
        sub     ax,ax
        mov     WORD PTR [bx].arbres1,ax
        mov     WORD PTR [bx].arbres1+2,ax
        mov     [bx].arbres2,ax
        mov     WORD PTR [bx].arbres3,ax
        mov     WORD PTR [bx].arbres3+2,ax
        mov     [bx].arbres4,al
        xchg    si,bx
        ; (si) -> ABIOS request block, (bx) -> ComInfo
        mov     dh,0                            ; use Start entry point
        mov     dl,DevHlp_ABIOSCommonEntry
        setES   DSEG
        call    es:[DevHlp]
        jc      disable_failed
        cmp     [si].arbrval,0
        je      ed10
disable_failed:
        mov     si,bx                           ; (si) -> ComInfo
        ComErr  <MCAEndDMA: call to ABIOS_FUNC_DISABLE_ARB failed>,CALLFAR

        ; Deallocate arbitration level.
        ; (si) -> ABIOS request block, (bx) -> ComInfo
ed10:
        mov     si,dealloc_ptr
        mov     [si].arbfunc,ABIOS_FUNC_DEALLOCARB
        mov     al,es:[di].pi_DMA_chan          ; actually arb level
        mov     [si].arb,al
        mov     [si].arbrval,0FFFFh
        sub     ax,ax
        mov     WORD PTR [si].arbres1,ax
        mov     WORD PTR [si].arbres1+2,ax
        mov     [si].arbres2,ax
        mov     WORD PTR [si].arbres3,ax
        mov     WORD PTR [si].arbres3+2,ax
        mov     [si].arbres4,al
        mov     dh,0                            ; use Start entry point
        mov     dl,DevHlp_ABIOSCommonEntry
        DevHelp
        jc      dealloc_failed
        cmp     [si].arbrval,0
        je      edix
dealloc_failed:
        mov     si,bx                           ; (si) -> ComInfo
        ComErr  <MCAEndDMA: call to ABIOS_FUNC_DEALLOC_ARB failed>,CALLFAR

edix:   xchg    si,bx
        RestoreReg      <es,ds>
        ret

Endproc MCAendDMA


Procedure FinishWriteReq,NEAR
        ASSUME cs:E_CSEG,ds:DSEG,es:NOTHING,ss:NOTHING

        les     di,[si].ci_w_rp                 ; (es:di) -> request packet
        cmp     [si].ci_w_to_move,0
        jne     fw50                            ; data still to move, req not done
        cmp     [si].ci_qout.ioq_count,0
        jne     fwx                             ; data still in queue, req not done

        ; Request is done
        ProcReady       CALLFAR                  ; put request on Ready list
        mov     [si].ci_w_rp._hi,0              ; no longer current write req

        SaveReg         <di>

        ; Start the next write request packet and trigger a new TxFIFO int,
        ; or if no next write request packet turn off the TxFIFO int
        CALLFAR GetNextWRP
        pushf                                   ; 'C' set if no more requests
        call    TriggerTX
        popf                                    ; restore 'C' from GetNextWRP

        RestoreReg      <di>                    ; (es:di) -> request packet
        jc      fwx                             ; no more requests

        ; More write requests, this is a chance to move data from user space
        ; to out que.
fw50:   mov     cx,[si].ci_qout.ioq_count
        cmp     cx,TX_MOVE_PROT
        ja      fwx                             ; not down to prot mode mark

        WHATMODE
        jc      fw60                            ; protect mode, go move data

        cmp     cx,TX_MOVE_REAL                 ; check real mode mark
        ja      fwx                             ; above real mode mark, don't move

        ; hit low water mark, move more data
fw60:   SET     [si].ci_flagx,FX_DATA_MOVED     ; mark data moved
        mov     cx,[si].ci_w_to_move            ; set up count

        CALLFAR WriteQueue                      ; move data into queue
                                                ; updates ci_w_to_move

fwx:    ret

EndProc FinishWriteReq

Procedure FinishReadReq,NEAR
        ASSUME cs:E_CSEG,ds:DSEG,es:NOTHING,ss:NOTHING

        SaveReg         <di>

        ; see if the current read request can be completed.
        cmp     [si].ci_r_rp._hi,0
        lje     frx                     ; no current read request

        mov     cx,[si].ci_qin.ioq_count; (cx) = number of bytes in queue
        cmp     cx,[si].ci_r_to_move
        jae     fr10                    ; enough to complete request

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

        ; BUGBUG - no wait mode
        ; 'no wait' mode can get here if there were queued read requests
        ; and then 'no wait' mode was set.
        ; Each queued request will remain blocked until the first interrupt
        ; of any type (rx, tx, mx).
        ; We could complete the current read request and flush all other read
        ; requests when 'no wait' mode is specified in Set DCB.

        cmp     al,F3_READ_TO_NW
        je      fr10                    ; no wait mode

        cmp     al,F3_READ_TO_WFS
        jne     fr20                    ; not wait for something mode

        jcxz    fr20                    ; wait for something mode, but no data

fr10:    ; enough to finish request (done, no wait or wait for something)
        les     di,[si].ci_r_rp         ; (es:di) -> request packet
        ASSUME  es:NOTHING
        ChkRPPtr
        ChkRPType       CMDINPUT

        test    es:[di].PktStatus,STDON
        jnz     frx                     ; already marked done

        or      es:[di].PktStatus,STDON ; mark request done
                                        ; (so we don't run it twice)

        ProcReady       CALLFAR         ; put request on 'ready' list
        jmp     SHORT frx

        ; may have enough data to move up to user buffer
        ; make mark higher for real mode hopeing for it to get moved in prot mode
fr20:    cmp    cx,RX_MOVE_PROT         ; (cx) = # of bytes in qin from above
        jb      frx                     ; below prot mode mark, don't move

        test    [si].ci_flagx,FX_DATA_MOVED
        jnz     frx                     ; data already moved on this interrupt
                                        ; set here and in TxInt

        WHATMODE
        jc      fr30                    ; protect mode, go move data

        cmp     cx,RX_MOVE_REAL         ; check real mode mark
        jb      frx                     ; below real mode mark, don't move

fr30:    or     [si].ci_flagx,FX_DATA_MOVED     ; mark data moved

        les     di,[si].ci_r_rp         ; (es:di) -> request packet
        ASSUME  es:NOTHING
        ChkRPPtr
        ChkRPType       CMDINPUT

        ; have to move data to user space
        sti                             ; allow interrupts while moving data
        mov     cx,[si].ci_r_to_move    ; set up count

        CALLFAR ReadQueue               ; move data to user space
                                        ; updates ci_r_to_move

frx:    RestoreReg      <di>
        ret

EndProc FinishReadReq

Procedure GetError,NEAR
;       ENTRY
;       EXIT            error condition in errshadow1 and errshadow2

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

        mov     dx,[si].ci_esp_address          ; (dx) -> ready reg
ges10:  in      al,dx
        test    al,ESP_RDY_CMD1
        jz      ges10
        add     dx,ESP_R_CMD1-ESP_R_RDY         ; (dx) -> cmd1 reg
        mov     al,ESP_CMD_GETERRS
        add     al,[si].ci_cmd_offset
        out     dx,al
        add     dx,ESP_R_RDY-ESP_R_CMD1         ; (dx) -> ready reg
ges20:  in      al,dx
        test    al,ESP_RDY_STATUS1
        jz      ges20
        add     dx,ESP_R_STATUS1-ESP_R_RDY      ; (dx) -> status1 reg
        in      al,dx
        mov     [si].ci_errshadow1,al

        add     dx,ESP_R_RDY-ESP_R_STATUS1      ; (dx) -> ready reg
ges30:  in      al,dx
        test    al,ESP_RDY_STATUS2
        jz      ges30
        add     dx,ESP_R_STATUS2-ESP_R_RDY      ; (dx) -> status2 reg
        in      al,dx

        mov     [si].ci_errshadow2,al
        ret

EndProc GetError


Procedure LineStatusError,NEAR
        ASSUME cs:E_CSEG,ds:DSEG,es:NOTHING,ss:NOTHING

        ; Handle DMA and PIO differently
        cmp     [si].ci_rx_request,DMA_REQ_DISABLE
        lje     lse200

        ; If this error caused last DMA transfer to be halted, update que ptrs
        cmp     si,[di].pi_DMA_outstanding
        ljne    lse200

        ; Get number of bytes actually transferred from ESP to inque
        cmp     Kernel_Type,ABIOS_COM
        je      lse183
        mov     dx,AT_DMA_CLEAR                 ; clear byte pointer before using count
        mov     al,0
        out     dx,al

        cmp     [di].pi_DMA_chan,3
        je      lse150
        mov     dx,AT_DMA_COUNT_CH1
        jmp     SHORT lse180

lse150: mov     dx,AT_DMA_COUNT_CH3

lse180: in      al,dx                   ; (al) = LSB bytes left to xfer
        mov     ah,al                   ; (ah) = LSB bytes left to xfer
        in      al,dx                   ; (al) = MSB bytes left to xfer
        xchg    al,ah                   ; (ax) = bytes left to xfer
        mov     dx,[di].pi_xfer_count   ; (dx) = bytes attempted to xfer
        jmp     SHORT lse185

lse183:
        mov     di,[si].ci_pinfo
        mov     al,[di].pi_DMA_chan     ; actually arb level
        setDS   HSEG                    ; tcount_ptr/tcount_rb in HSEG
        mov     bx,tcount_ptr
        mov     [bx].func,ABIOS_FUNC_XFERSTATUS
        mov     [bx].tcarb,al
        xchg    si,bx
        ; (si) -> ABIOS request block, (bx) -> ComInfo
        mov     dh,0                            ; use Start entry point
        mov     dl,DevHlp_ABIOSCommonEntry
        push    es
        setES   DSEG
        call    es:[DevHlp]
        pop     es
        jc      status_failed
        cmp     [si].arbrval,0
        je      lse184
status_failed:
        mov     si,bx                           ; (si) -> ComInfo block
        ComErr  <LineStatusError : call to ABIOS_FUNC_XFER_STATUS failed>,CALLFAR
lse184:  mov     dx,[si].tcount._lo

lse185:
        setDS   DSEG                    ; finished with req block, put it back
        sub     dx,ax                   ; (dx) = bytes ACTUALLY xferred
        ; (dx) = bytes ACTUALLY transferred
        ; Update inque pointers
        add     [si].ci_qin.ioq_count,dx        ; adjust que count
        add     [si].ci_qin.ioq_in,dx
        mov     ax,[si].ci_qin.ioq_in
        cmp     ax,[si].ci_qin.ioq_end          ; adjust for wrap if necessary
        jne     lse190                            ; did not wrap
        mov     ax,[si].ci_qin.ioq_base         ; wrap
        mov     [si].ci_qin.ioq_in,ax
lse190:  mov     [di].pi_DMA_outstanding,0

        ; Que ptrs are updated, now take care of flags and replacement chars
lse200:  test    [si].ci_errshadow1,ESP_ERR1_FRAME OR ESP_ERR1_PARITY
        jz      lse220
        mov     al,[si].ci_errshadow1
        xor     ah,ah                           ; don't need errs in high byte
        and     ax,CE_RX_PARITY OR CE_FRAME     ; mask error bits
        or      [si].ci_comerr,ax               ; combine error bits with
                                                ; previous errors
        SET     [si].ci_event,EV_ERR            ; mark line status error

        test    [si].ci_dcb_flags2,F2_ERR_CHAR
        jz      lse220

        ; Last byte transferred into rx que by ESP was byte containing parity
        ; or framing error. Now replace this last byte with error char.
lse210: mov     bx,[si].ci_qin.ioq_in
        cmp     bx,[si].ci_qin.ioq_base         ; in == base?
        jne     lse215                           ; didn't wrap when placing
                                                ;  byte in rx que
        mov     bx,[si].ci_qin.ioq_end          ; 'unwrap' back to end of que

lse215: dec     bx                              ; (bx) -> last byte in que
        mov     [si].ci_qin.ioq_in,bx           ; in points to position for
                                                ;  replacement char
        dec     [si].ci_qin.ioq_count
        mov     al,[si].ci_dcb_ErrChar
        CALLFAR WriteQueueByte                  ; also updates in ptr

        ; check for received break
lse220: test    [si].ci_errshadow1,ESP_ERR1_BRK
        jz      lsex

        SET     [si].ci_event,EV_BREAK
        test    [si].ci_dcb_flags2,F2_BRK_CHAR
        ljz     lsex

        ; Last byte transferred into rx que by ESP was byte containing break.
        ; Now replace this last byte with error char.
        mov     bx,[si].ci_qin.ioq_in
        cmp     bx,[si].ci_qin.ioq_base         ; in == base?
        jne     lse235                           ; didn't wrap when placing err
                                                ; byte into que
        mov     bx,[si].ci_qin.ioq_end

lse235: dec     bx                              ; (bx) -> last byte in que
        mov     al,[si].ci_dcb_BrkChar
        mov     [si].ci_qin.ioq_in,bx           ; in points to position for
                                                ;  replacement char
        CALLFAR WriteQueueByte                  ; also updates in ptr

lsex:   ret

EndProc LineStatusError

Procedure TurnOffESP,NEAR
        ASSUME cs:E_CSEG,ds:DSEG,es:NOTHING,ss:NOTHING

        ; Reset service mask so that ESP doesn't interrupt when RxFIFO
        ; trigger is reached
        ; DO WE FLAG AS OVERRUN?
        SET     [si].ci_event,CE_SW_OVERRUN

ifdef PERFVIEW
        pvw_SW_Overrun              ;inc PerfView flag          ;@PVW
endif

        mov     [si].ci_rx_state,RX_STATE_HOLDING
        cmp     [si].ci_cmd_offset,0
        jne     toe80
        CLR     [di].pi_svcmask,ESP_SID_RX1
        jmp     SHORT toe90

toe80:  CLR     [di].pi_svcmask,ESP_SID_RX2
toe90:  mov     dx,[si].ci_esp_address
toe100: in      al,dx
        test    al,ESP_RDY_CMD1
        jz      toe100

        mov     al,ESP_CMD_SETSVCMASK
        add     al,[si].ci_cmd_offset
        add     dx,ESP_R_CMD1-ESP_R_RDY
        out     dx,al

        add     dx,ESP_R_RDY-ESP_R_CMD1
toe110: in      al,dx
        test    al,ESP_RDY_CMD2
        jz      toe110

        add     dx,ESP_R_CMD2-ESP_R_RDY
        mov     al,[di].pi_svcmask
        out     dx,al

        mov     dx,[si].ci_esp_address
toe115: in      al,dx
        test    al,ESP_RDY_CMD1
        jz      toe115
        add     dx,ESP_R_CMD1-ESP_R_RDY
        mov     al,ESP_CMD_GDIPS
        out     dx,al

        add     dx,ESP_R_RDY-ESP_R_CMD1
toe117: in      al,dx
        test    al,ESP_RDY_STATUS1
        jz      toe117

        add     dx,ESP_R_STATUS1-ESP_R_RDY
        in      al,dx

        ret

EndProc TurnOffESP


Procedure ResetTriggerLevel,NEAR
        ASSUME cs:E_CSEG,ds:DSEG,es:NOTHING,ss:NOTHING

        mov     dx,[si].ci_esp_address                  ; (dx) -> ready reg
rtl10:  in      al,dx
        test    al,ESP_RDY_CMD1
        jz      rtl10
        add     dx,ESP_R_CMD1-ESP_R_RDY                 ; (dx) -> cmd1 reg
        mov     al,ESP_CMD_SETTRIGGER
        add     al,[si].ci_cmd_offset
        out     dx,al
        add     dx,ESP_R_RDY-ESP_R_CMD1                 ; (dx) -> ready reg
rtl20:  in      al,dx
        test    al,ESP_RDY_CMD2
        jz      rtl20
        add     dx,ESP_R_CMD2-ESP_R_RDY                 ; (dx) -> cmd2 reg
        mov     ax,[si].ci_rx_trigger
        xchg    ah,al                                   ; send MSB first
        out     dx,al
        add     dx,ESP_R_RDY-ESP_R_CMD2                 ; (dx) -> ready reg
rtl30:  in      al,dx
        test    al,ESP_RDY_CMD2
        jz      rtl30
        add     dx,ESP_R_CMD2-ESP_R_RDY                 ; (dx) -> cmd2 reg
        mov     al,ah                                   ; send LSB next
        out     dx,al
        add     dx,ESP_R_RDY-ESP_R_CMD2                 ; (dx) -> ready reg
rtl40:  in      al,dx
        test    al,ESP_RDY_CMD2
        jz      rtl40
        add     dx,ESP_R_CMD2-ESP_R_RDY                 ; (dx) -> cmd2 reg
        mov     al,HIGH(ESP_DEF_TRIGGER_TX)
        out     dx,al
        add     dx,ESP_R_RDY-ESP_R_CMD2                 ; (dx) -> ready reg
rtl50:  in      al,dx
        test    al,ESP_RDY_CMD2
        jz      rtl50
        add     dx,ESP_R_CMD2-ESP_R_RDY                 ; (dx) -> cmd2 reg
        mov     al,LOW(ESP_DEF_TRIGGER_TX)
        out     dx,al

        ret

EndProc ResetTriggerLevel

Procedure RxPIO,NEAR
        ASSUME cs:E_CSEG,ds:DSEG,es:NOTHING,ss:NOTHING

        ; Transfer bytes via PIO from ESP's RX register to driver's in que
        ; Expecting number of bytes to transfer in CX.
        inc     cx                              ; byte count from ESP doesn't
                                                ; include byte in the RX reg
        cmp     [si].ci_cmd_offset,0
        jne     rp10
        mov     bl,ESP_RDY_RX1
        mov     bh,ESP_R_RX1-ESP_R_RDY
        jmp     SHORT rp20
rp10:   mov     bl,ESP_RDY_RX2
        mov     bh,ESP_R_RX2-ESP_R_RDY

        ; (bh) = offset of proper rx register; (bl) = ready register mask
rp20:   mov     dx,[si].ci_esp_address
rp30:   in      al,dx
        test    al,bl                           ; is rx register ready?
        jz      rp30
        add     dl,bh                           ; (dx) = rx register
        in      al,dx                           ; (al) = receive byte

        ; If the byte we just got from RX reg contained an error (Parity,
        ; Framing, or Break), then ESP won't give us the next byte until
        ; the error status is cleared.
        push    bx                              ; save mask and offset
        mov     bx,ax                           ; (bx) = (ax) = new rx byte
        mov     dx,[si].ci_esp_address
        add     dx,ESP_R_SID
        in      al,dx
        test    al,ESP_SID_ERROR1 OR ESP_SID_ERROR2
        jz      rp40

        cmp     [si].ci_cmd_offset,0
        jne     rp35
        test    al,ESP_SID_ERROR1
        jz      rp40
        jmp     SHORT rp37

rp35:   test    al,ESP_SID_ERROR2
        jz      rp40

rp37:   call    GetError                        ; fills in errshadow1 and errshadow2
        test    [si].ci_errshadow1,ESP_ERR1_BRK OR ESP_ERR1_FRAME OR ESP_ERR1_PARITY
        jz      rp40                            ; nothing       with the byte after all!
        call    LineStatusError                 ; handle the error byte
        clc
        jmp     SHORT rp50                      ; don't add to queue

rp40:   mov     ax,bx
        call    ESPWriteQueueByte               ; (al) = byte to add to in queue

rp50:   pop     bx                              ; restore offset and mask
        jc      rp60                            ; in queue is full
        loop    rp20                            ; got all bytes yet?

        ; ESP is empty or driver's in que is full
rp60:   call    FinishReadReq

        ret

EndProc RxPIO

Procedure ESPWriteQueueByte,NEAR
        ASSUME cs:E_CSEG,ds:DSEG,es:NOTHING,ss:NOTHING

        test    [si].ci_flagx,FX_LAST_CLOSE
        jnz     wqb2            ; last close in progress, drop character

        ; reset timeout anytime a character is put into the receive queue
        mov     bx,[si].ci_r_to_start   ; get start value for timeout
        mov     [si].ci_r_to,bx         ; reset timeout countdown

        mov     bx,[si].ci_qin.ioq_in
        mov     [bx],al                         ; put byte in queue
        inc     bx                              ; adjust in pointer
        cmp     bx,[si].ci_qin.ioq_end
        jne     wqb1                            ; did not wrap

        mov     bx,[si].ci_qin.ioq_base  ; wrapped

wqb1:   cmp     bx,[si].ci_qin.ioq_out
        je      wqb2                            ; (in + 1) == out
                                                ; queue was already full

        mov     [si].ci_qin.ioq_in,bx           ; update in pointer
        inc     [si].ci_qin.ioq_count           ; adjust count

        or      [si].ci_event,EV_RX_CHAR        ; flag rx character

        clc
        ret

wqb2:   stc
        ret

EndProc ESPWriteQueueByte

E_CSEG  ENDS

RSEG    SEGMENT
        ASSUME cs:RSEG

Procedure ESPInt1,FAR
        ASSUME ds:HSEG,es:NOTHING,ss:NOTHING

        setDS   DSEG
        mov     di,ESP1         ; pointer to ESP1 info structure
        jmp     ESPInt

Entry ESPInt2,,,nocheck
        ASSUME ds:HSEG,es:NOTHING,ss:NOTHING

        setDS   DSEG
        mov     di,ESP2         ; pointer to ESP2 info structure
        jmp     ESPInt

EndProc ESPInt1

RSEG    ENDS

END
        ; Unfortunately, ABIOS request blocks are all in HSEG, since
        ; DevHlp_ABIOSCommonEntry requires them in driver's RESIDENT data seg.
        ; This requires (slow) segment overrides when loading request blocks.
        setES   DSEG


