;       SCCSID = @(#)atqueue.asm        6.3 91/04/22
; ***************************************************************************
; *
; *                       IBM/Microsoft Confidential
; *
; *                 Copyright (c) IBM Corporation  1987, 1990
; *                 Copyright (c) Microsoft Corp.  1987, 1990
; *                           All Rights Reserved
; *
; ***************************************************************************

        PAGE    ,132
        .286p

        TITLE   com01.sys - Asynchronous Communication Device Driver
        NAME com01

;       Bryan Diehl
;       David Gilman

;***    atqueue.asm
;
;       The device driver's internal buffers are maintained as circular
;       queues. This file contains the routines necessary to access and
;       maintain these queues. There are routines to transfer data to
;       and from user space, as well as routines used by the interrupt
;       handlers. These routines have been specialized to convert physical
;       to virtual addresses and to recognize when flow control states
;       need to be changed.
;
;       The routines that exist in this file are:
;
;               + ReadQueueByte - read a single byte from the output queue
;               + WriteQueueByte - write a single byte to the input queue
;               + ReadQueue - read a specified number of bytes into user space
;               + WriteQueue - write a specified number of bytes from user space
;               + CopyQueue - support routine for ReadQueue and WriteQueue
;
;       Modification History
;
;       DJG     01//16/87       Re-written to use the circular buffer
;                               code lifted from dos\pipe.asm
;
;       YN      05/25/89        MVDM Support - @VDM
;
;       ACW     04/16/91        @PVW Added perfview counters/timers
;
;       WDM     04/21/94        82548 - pvwxport.inc now included in atcom.inc
;




.xlist
;  82548   include pvwxport.inc            ;@PVW
include devhlp.inc
include devsym.inc
include basemaca.inc
include realmac.inc

.list

include atcom.inc

        extrn   DisableRemoteTX:near
        extrn   EnableRemoteTX:near
        EXTRN   DevHlp:DWORD            ; DevHlp entry point

CSEG    SEGMENT
        ASSUME  cs:CSEG

;**     ReadQueueByte - read a single byte from the qout IO_Queue
;
;       ReadQueueByte removes one character from the output queue
;       and returns it to the caller in (al).
;
;       ENTRY   (ds:si) -> ComInfo
;
;       EXIT    if 'C' clear
;                       (al) = byte
;                       ioq_count is decremented by one
;               else
;                       queue is empty
;
;       USES    bx

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

        ChkComInfoPtr

        mov     bx,[si].ci_qout.ioq_out
        cmp     bx,[si].ci_qout.ioq_in
        stc                                     ; assume the queue is empty
        je      rqbx                            ; queue is empty (out == in)

        mov     al,[bx]                         ; (al) = byte
        dec     [si].ci_qout.ioq_count          ; adjust count
        jnz     rqb10                           ; didn't take last char

        or      [si].ci_event,EV_TX_EMPTY       ; show that txq emptied

rqb10:  inc     bx                              ; adjust out pointer
        cmp     [si].ci_qout.ioq_end,bx         ; clears carry (end >= bx)
        jne     rqb20                           ; did not hit end of queue

; Hit the end of the queue, wrap out pointer (out == base).

        mov     bx,[si].ci_qout.ioq_base        ; (bx) = base of queue
rqb20:  mov     [si].ci_qout.ioq_out,bx         ; reset the output pointer

rqbx:   ret

EndProc ReadQueueByte


;**     WriteQueueByte - write a single byte to the qin IO_Queue
;
;       WriteQueueByte takes a single character in (al) and puts
;       it into the input queue.
;
;       If a Last Close is in progress, nothing is done.
;
;       The read timeout countdown is reset.
;       The byte is put into the queue. However if the queue
;       is actually full the pointers are not adjusted. This can be
;       done since a 'full' queue has one empty slot (used to differentiate
;       between full and empty).
;
;       ENTRY   (ds:si) -> ComInfo
;               (al) = byte
;
;       EXIT    if Last Close in progress
;                   return
;               reset read timeout countdown
;               if queue is not full
;                   byte enqued
;                   ioq_count is incremented by one
;                   if queue is above highwater mark
;                       DisableRemoteTX
;               else queue is full
;                   if ErrChar Replacement enabled
;                       last character in queue is replaced with
;                       the error replacement character
;
;       USES    ax bx dx

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

        ChkComInfoPtr

        test    [si].ci_flagx,FX_LAST_CLOSE
        jnz     wqbx            ; 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


; If standard port, we must handle flow control: if the receive queue is
; above the high water mark, we must disable the remote transmitter.

        cmp     [si].ci_qin.ioq_count,RX_HIGH_HS
        jbe     wqbx                    ; queue is below high water mark for HS

        call    DisableRemoteTX         ; may send XOFF and/or drop HW HS lines

wqbx:   ret

; If the queue is full then we may have to show the user that there
; was a queue overrun. That is, we may have to perform error character
; replacement.

wqb2:   or      [si].ci_comerr,CE_SW_OVERRUN    ; flag rx queue overrun
ifdef PERFVIEW
        pvw_SW_Overrun                          ;@PVW increment perfview cntr
endif
        or      [si].ci_event,EV_ERR            ; flag rx queue overrun

        test    [si].ci_dcb_flags2,F2_ERR_CHAR
        jz      wqbx                            ; error replacement not on

        mov     bx,[si].ci_qin.ioq_in           ; (bx) = in pointer
        cmp     bx,[si].ci_qin.ioq_base
        jne     wqb3                            ; we didn't wrap when placing
                                                ; the byte in the queue

        mov     bx,[si].ci_qin.ioq_end          ; 'unwrap' back to end

wqb3:   dec     bx                              ; (bx) -> last byte in queue
        mov     al,[si].ci_dcb_ErrChar
        mov     [bx],al                         ; replace it with the error
                                                ; character

        jmp     SHORT wqbx

EndProc WriteQueueByte


;**     ReadQueue - read from the qin IO_Queue and write to user memory
;
;       ReadQueue will transfer no more than (cx) bytes from the input
;       queue to user memory. It performs the necessary address conversion
;       as well as recognition of when flow control needs to be changed.
;
;       ENTRY   (ds:si) -> ComInfo
;               (es:di) -> Read Request packet
;               (cx) = byte count
;
;       EXIT    (bx) = number actually moved
;               (cx) = residual byte count
;
;       NOTE    On exit (bx) + (cx) = (cx) on input.
;
;       USES    ax bx dx

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

        ChkComInfoPtr
        ChkRPPtr

; Get destination of transfer.

        SaveReg         <es,di>
;       mov     ax,es:[di].IoPData._hi
;       mov     bx,es:[di].IoPData._lo          ; (ax:bx)= physical xfer address
;       mov     dx,(0100h OR DevHlp_PhysToVirt)
;       DevHelp
;       jnc     rdq00
;
;       ComErr  <ReadQueue : PhysToVirt invalid address>
        les     di,es:[di].IoPData              ; (es:di)-> virtual xfer address

rdq00:  SaveReg         <cx>

        mov     bx,cx                   ; (bx) = byte count

rdq0:   mov     cx,[si].ci_qin.ioq_in
        sub     cx,[si].ci_qin.ioq_out
        jz      rdq5                    ; buffer empty, go return
        jnc     rdq2                    ; can transfer up to in ptr
        mov     cx,[si].ci_qin.ioq_end
        sub     cx,[si].ci_qin.ioq_out  ; can only xfer to end of buffer

rdq2:   cmp     cx,bx
        jbe     rdq3                    ; xfer len smaller than byte count
        mov     cx,bx                   ; don't xfer more than byte count

rdq3:   push    si                      ; (tos) = ComInfo
        mov     si,[si].ci_qin.ioq_out  ; (si) = offset part of buffer ptr
                                        ; (ds:si) -> source of data
                                        ; (es:di) -> dest for copy
                                        ; (cx) = byte count for this copy
        sub     bx,cx                   ; (bx) = bytes remaining after copy
        call    CopyQueue               ; (ds:si) is adjusted
                                        ; (es:di) is adjusted
                                        ; (cx) = zero
        mov     cx,si                   ; (cx) = offset of new out ptr
        pop     si                      ; (ds:si) -> ComInfo
        ChkComInfoPtr

        cmp     cx,[si].ci_qin.ioq_end
        jb      rdq4                    ; didn't hit end of buffer
        mov     cx,[si].ci_qin.ioq_base ; hit end, pointer wraps around

rdq4:   mov     [si].ci_qin.ioq_out,cx  ; save the new pointer
        or      bx,bx
        jnz     rdq0                    ; more bytes to xfer

        ; Release virtual address
rdq5:
;       mov     dl,DevHlp_UnPhysToVirt
;       DevHelp

        RestoreReg      <cx>            ; (cx) = requested transfer

        RestoreReg      <di,es>         ; (es:di) -> request packet
        ASSUME  es:NOTHING
        ChkRPPtr

        sub     cx,bx                   ; (cx) = actual transfer
        add     es:[di].IOpData._lo,cx  ; adjust physical address pointer
;       adc     es:[di].IOpData._hi,0
        sub     [si].ci_qin.ioq_count,cx; adjust ioq_count

        xchg    cx,bx                   ; (cx) = residual byte count
                                        ; (bx) = actual transfer
        mov     [si].ci_r_to_move,cx    ; update number left to move


        ; If port is STANDARD, may have to flow on remote transmitter.
rdq90:  cmp     [si].ci_qin.ioq_count,RX_LOW_HS
        jae     rdqx                    ; not below low water mark for HS

        SaveReg         <cx>
        call    EnableRemoteTX          ; below low water, enable remote TXer
        RestoreReg      <cx>            ; (cx) = residual byte count

rdqx:   ret

EndProc ReadQueue


;**     WriteQueue - read from user memory and write to the qout IO_Queue
;
;       WriteQueue will transfer no more than (cx) bytes from user memory
;       into the output queue. It performs the necessary address conversion.
;
;       ENTRY   (ds:si) -> ComInfo
;               (es:di) -> Write Request packet
;               (cx) = byte count
;
;       EXIT    (bx) = number actually moved
;               (cx) = residual byte count
;
;       NOTE    On exit (bx) + (cx) = (cx) on input.
;
;       USES    ax dx si

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

        ChkComInfoPtr
        ChkRPPtr

        SaveReg         <si>
        SaveReg         <es,di>

; Get physical address of xfer source

;       mov     ax,es:[di].IoPData._hi
;       mov     bx,es:[di].IoPData._lo  ; (ax:bx) = physical addr of user data
;
;       push    ds
;       pop     es
;       ASSUME  es:DSEG
;       xchg    si,di
;
;       mov     dx,(0000 OR DevHlp_PhysToVirt)
;       DevHelp
;       jnc     wrq00
;
;       ComErr  <WriteQueue : PhysToVirt invalid address>

        push    si
        push    ds
        lds     si,es:[di].IoPData ; (ds:si) -> virtual xfer source
        pop     es
        pop     di                 ; (es:di) -> ComInfo
        ASSUME  es:DSEG

wrq00:  SaveReg         <cx>
        mov     bx,cx                           ; (bx) = byte count

;       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.

wrq0:   mov     cx,es:[di].ci_qout.ioq_in
        mov     dx,es:[di].ci_qout.ioq_out
        sub     cx,dx                   ; (cx) = -(out - in)
        jb      wrq1                    ; in < out, can only go to (out - 1)
        add     cx,dx                   ; (cx) = ioq_in
        sub     cx,es:[di].ci_qout.ioq_end
        cmp     dx,es:[di].ci_qout.ioq_base
        jne     wrq2

wrq1:   inc     cx                      ; 'subtract' from negated count

wrq2:   neg     cx                      ; (cx) = transfer count

        jcxz    wrq5                    ; no room, return
        cmp     cx,bx
        jbe     wrq3                    ; xfer len smaller than request
        mov     cx,bx                   ; don't xfer more than requested

;       (es:di) -> ComInfo
;       (cx) = byte count for this xfer
;       (ds:si) -> source

wrq3:   push    di

        mov     di,es:[di].ci_qout.ioq_in       ; (es:di) -> dest
                                                ; (ds:si) -> source
                                                ; (cx) = byte count for copy
        sub     bx,cx                           ; (bx) = bytes remaining
                                                ; after copy
        call    CopyQueue                       ; (ds:si) is adjusted
                                                ; (es:di) is adjusted
                                                ; (cx) = zero
        mov     cx,di                           ; (cx) = offset part of
                                                ; new out ptr
        pop     di                              ; (es:di) -> ComInfo
        cmp     cx,es:[di].ci_qout.ioq_end
        jb      wrq4                            ; didn't hit end of buffer
        mov     cx,es:[di].ci_qout.ioq_base     ; hit end, handle wrap around

wrq4:   mov     es:[di].ci_qout.ioq_in,cx       ; and save the new pointer
        or      bx,bx
        jnz     wrq0                            ; more bytes to xfer

; Release virtual address

wrq5:   mov     ax,es
        mov     ds,ax                           ; (ds) -> DD data
        ASSUME  ds:DSEG

;       mov     dl,DevHlp_UnPhysToVirt
;       DevHelp

        RestoreReg      <cx>                    ; (cx) = requested transfer

        sub     cx,bx                           ; (cx) = actual transfer
        add     es:[di].ci_qout.ioq_count,cx    ; adjust ioq_count

        RestoreReg      <di,es>         ; (es:di) -> request packet
        RestoreReg      <si>            ; (ds:si) -> ComInfo
        ChkComInfoPtr
        ChkRPPtr

        add     es:[di].IOpData._lo,cx  ; adjust physical address pointer
;       adc     es:[di].IOpData._hi,0
        xchg    cx,bx                   ; (cx) = byte count
                                        ; (bx) = actual transfer
        mov     [si].ci_w_to_move,cx    ; update number left to move

        ret

EndProc WriteQueue


;**     CopyQueue - move contents of memory
;
;       We copy bytes from one place to another, optimizing for
;       word alignment (word aligned moves are faster).
;
;       ENTRY   (ds:si) -> source
;               (es:di) -> destination
;               (cx)    = byte count
;
;       EXIT    (ds:si) -> one past last byte copied in source
;               (es:di) -> one past last byte copied in dest
;               (cx) = 0
;
;       NOTE    Move is assumed to be non-overlapping
;               Length is assumed to be non-zero
;
;       USES    si di cx flags

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

        jcxz    cpqer

        test    si,1
        jz      cpq1            ; source is even
        test    di,1
        jz      cpq1            ; dest is even
        movsb                   ; both are odd, copy one byte to make both even
        dec     cx              ; and adjust the count
        jz      cpq2            ; it was only one byte

cpq1:   shr     cx,1            ; word count, carry set if count was odd
        rep     movsw           ; mov cx words (doesn't jam carry flag)
        jnc     cpq2            ; count was even, we're done
        movsb

cpq2:   ret

cpqer:
        ComErr  <CopyQueue : count is zero.>

EndProc CopyQueue


CSEG    ENDS

        END
