.386p
        page 58,132
;=============================================================================
        title   D M A S E R V - DMA/bus master services under Virtual 8086 mode
;=============================================================================
;==
;== (C) Copyright MICROSOFT Corp. 1989-1991
;== (C) Copyright COMPAQ Computer Corp. 1989-1991
;==
;==     Title:  EMM386.EXE - MICROSOFT Expanded Memory Manager 386 Driver
;==
;==     Module: DMAServ  - DMA/bus master services under Virtual 8086 mode
;==
;==     Version: 1.00
;==
;==     Date:   September 22,1989
;==
;==     Author: Leo Cohen
;==
;=============================================================================
;==
;==     Change Log:
;==
;==     DATE     REVISION       Description
;==     -------- --------       --------------------------------------------
;==     09/22/89 0.00           Original
;==
;=============================================================================
;==
;==   Functional Description:
;==
;==   This module provides DMA/bus master services while in Virtual 8086 mode.
;==
;=============================================================================
;==     P U B L I C   D E C L A R A T I O N S
;=============================================================================
        public  pINT4BhHandler
;;      public  rINT4BhHandler
;=============================================================================
;==     L O C A L   C O N S T A N T S
;=============================================================================
        include allocmem.inc
        include vdmseg.inc
        include vdmsel.inc
        include desc.inc
        include dma.inc
        include page.inc
        include oemdep.inc
        include vm386.inc
        include emm386.inc
        include emmfunct.inc
        include emmdata.inc
;=============================================================================
;==     E X T E R N A L   R E F E R E N C E S
;=============================================================================
_TEXT   segment
        extrn   ReflectInterrupt:near
        extrn   MoveBuffer:near
        extrn   ContigCheck:near
_TEXT   ends

_DATA   segment
        extrn   DMARegSav:byte
_DATA   ends
;=============================================================================
;==     L O C A L   D A T A
;=============================================================================
_DATA   segment

DMABuffID   dw  0               ; Current DMA buffer ID
DBSDisTable db  8 dup (0)       ; disable translation count per channel

DBSTable label  word
        dw      offset _TEXT:DBSInvalidFunction ; AL=0  reserved
        dw      offset _TEXT:DBSInvalidFunction ; AL=1  reserved
        dw      offset _TEXT:DBSGetVersion      ; AL=2  Get Version Function
        dw      offset _TEXT:DBSLockDMARegion   ; AL=3  Lock DMA Region
        dw      offset _TEXT:DBSUnlockDMARegion ; AL=4  Unlock DMA Region
        dw      offset _TEXT:DBSScatGathLock    ; AL=5  Scatter/Gather Lock Region
        dw      offset _TEXT:DBSScatGathUnlock  ; AL=6  Scatter/Gather Unlock Region
        dw      offset _TEXT:DBSReqDMABuffer    ; AL=7  Request DMA Buffer
        dw      offset _TEXT:DBSRelDMABuffer    ; AL=8  Release DMA Buffer
        dw      offset _TEXT:DBSCopyToBuffer    ; AL=9  Copy to DMA Buffer
        dw      offset _TEXT:DBSCopyFromBuffer  ; AL=10 Copy from DMA Buffer
        dw      offset _TEXT:DBSDisDMATrans     ; AL=11 Disable DMA Translation
        dw      offset _TEXT:DBSEnaDMATrans     ; AL=12 Enable DMA Translation
DBSLastFunc     equ     ($-DBSTable)/2
        dw      offset _TEXT:DBSInvalidFunction ; AL=>13 reserved
_DATA   ends
;=============================================================================
;==     C O D E  S E G M E N T
;=============================================================================
_TEXT   segment
        assume  cs:_TEXT,ds:_DATA,es:_DATA,ss:STACK
;==============================================================================
;==
;==  DBSdipatcher - Dispatches DMA/bus master virtual mode services functions
;==
;==  Entry: (Protected Mode Ring 0)
;==     AH = 81h
;==     AL = Function number
;==     DX = Flags
;==     ES:EDI    = DMA Descriptor Structure (DDS)
;==     BX:CX/ECX = May be a buffer offset
;==     EBP= Virtual mode stack 0 image
;==     DS = _DATA
;==     GS = R_CODE
;==
;==  Exit:  (Protected Mode Ring 0)
;==     AL = Status
;==     CY = clear if OK, set if error
;==
;==============================================================================
DBSdispatcher   proc    near

        cmp     al,2                    ;Q: Get version function?
        je      short DBSGetVersion     ; Y: go get it
                                        ; N: dispatch to proper function
        pushad
        movzx   esp,sp                  ; access only 64K
        mov     [ebp][-4],esp           ; pointer to PUSHAD image
        push    es

        push    DATA32_GSEL             ; access all of memory
        pop     es

        cmp     al,DBSLastFunc          ;Q: Valid function?
        jb      short DBSDindex         ; Y: index into function
        mov     al,DBSLastFunc          ; N: invalid function

DBSDindex:
        and     eax,0FFh                ; clear all but function number
        call    [DBSTable][eax*2]       ; vector to proper function
        mov     ebx,[ebp][-4]           ; pointer to PUSHAD image
        mov     byte ptr ss:[ebx].reg_EAX,al ; return correct status code

        and     [ebp][VTFO].VMTF_EFLAGS,not FLAGS_CF ; assume success
        or      al,al                                ;Q: Need to set carry?
        jz      short DBSDexit                       ; N: exit
        or      [ebp][VTFO].VMTF_EFLAGS,FLAGS_CF     ; Y: set carry

DBSDexit:
        pop     es
        popad
        ret
DBSdispatcher   endp
;==============================================================================
;==
;==  DBSInvalidFunction - Invalid function number.
;==
;==  Entry: (Protected Mode Ring 0)
;==     AL = 0,1,13,14,15,...
;==
;==  Exit:  (Protected Mode Ring 0)
;==     AL = 15, illegal function
;==     CY = set
;==
;==============================================================================
DBSInvalidFunction proc near

        mov     al,eDBSInvalidFunc      ; error return, invalid function
        ret

DBSInvalidFunction      endp
;==============================================================================
;==
;==  DBSGetVersion - returns version of the DMA bus/master services.
;==
;==  Entry: (Real Mode)
;==     AL = 2
;==     DX = 0
;==
;==  Exit:  (Real Mode)
;==     AH = Major version
;==     AL = Minor version
;==     BH = OEM number
;==     BL = OEM revision
;==
;==     SI:DI = Maximum DMA buffer size
;==     DX = Bit 1 set: if DMA buffer is located below 1 megabyte
;==
;==============================================================================
fDBSLegal=0
DBSGetVersion   proc    near

        test    dx,not fDBSLegal        ;Q: Illegal flags used?
        jnz     short DGVbadFlags       ; Y: return error

        mov     ah,DBSMajVer            ; major version
        mov     al,DBSMinVer            ; minor version

        mov     di,word ptr [DMABufferSize]     ; size of DMA buffer
        mov     si,word ptr [DMABufferSize+2]
        mov     bx,DBSNumOEM            ; OEM number
        mov     cx,DBSRevOEM            ; OEM revision

        xor     dx,dx                   ; no flags
        test    [DMAFlags],fDMABuffXT   ;Q: Buffer below 1 meg?
        jz      short DGVexit           ; N: exit
        mov     dx,DBSDMAXT             ; Y: PC/XT architecture supported

DGVexit:
        and     [ebp][VTFO].VMTF_EFLAGS,not FLAGS_CF ; success: clear carry
        ret

DGVbadFlags:
        mov     al,eDBSInvalidFlag              ; error return
        or      [ebp][VTFO].VMTF_EFLAGS,FLAGS_CF; set carry
        ret
DBSGetVersion   endp
;==============================================================================
;==
;==  DBSLockDMARegion - Attempts to get a physical buffer.
;==
;==  Entry: (Protected Mode Ring 0)
;==     AL = 3
;==     DX = Flags: bit
;==                 1 = copy to buffer (invalid: 2=1/DMA buffer not used)
;==                 2 = If not contiguous DMA buffer should not be allocated
;==                 3 = auto remap invalid
;==                 4 = 64K aligned
;==                 5 = 128K aligned
;==     ES:(E)DI = pointer to DDS
;==
;==  Exit:  (Protected Mode Ring 0)
;==     AL = status value
;==     CY clear:   0 = OK
;==     CY set:     1 = region not contiguous
;==                 2 = alignment crossed
;==                 3 = unable to lock pages
;==                 4 = no buffer available
;==                 5 = DMA buffer is too small
;==                 6 = DMA buffer currently in use
;==                10h= illegal flags requested
;==
;==============================================================================
fDBSLegal=fDBSBufferCopy+fDBSDisAutoBuff+fDBSDisAutoRem+fDBS64kAlign+fDBS128kAlign
DBSLockDMARegion proc   near

        test    dx,not fDBSLegal        ;Q: Illegal flags used?
        jz      short DLDRptr           ; N: continue
        mov     al,eDBSInvalidFlag      ; Y: error return
        jmp     DLDRexit

DLDRptr:
        movzx   edi,di                  ; convert to 32 bit pointer
        call    GetDDSPtr               ; convert V86 ES:EDI to protected ES:EBX
        mov     es:[ebx].DDSBuffID,0    ; asssume DMA buffer not used

        call    GetUserBufferPtr        ; ESI/ECX is user buffer ptr/size
        mov     eax,esi

        mov     edi,edx                 ; save flags
        xor     edx,edx                 ; assume no boundary restrictions

        test    edi,fDBS64kAlign+fDBS128kAlign ;Q: Any boundary conditions?
        jz      short DLDRbound                ; N: continue with no restrictions
        mov     edx,20000h                     ; Y: assume a 128K restriction

        test    edi,fDBS64kAlign        ;Q: Is it a 64K restriction?
        jz      short DLDRbound         ; N: go with 128K restriction
        shr     edx,1                   ; Y: change to 64K restriction

DLDRbound:
        call    ContigCheck             ;Q: Is user buffer OK?
        jnc     short DLDRok            ; Y: easy, fill in DDS
        xchg    ecx,es:[ebx].DDSSize    ; N: contiguous size in DDS

        test    edi,fDBSDisAutoBuff     ;Q: Disable auto DMA buffer allocation?
        jz      short DLDRsizeDMAbuff   ; N: check if DMA buffer size is OK
        mov     al,eDBSNotContig        ; Y: assume user buffer not contiguous

        or      edx,edx                 ;Q: Boundary crossing?
        jz      short DLDRexit          ; N: buffer is not contiguous
        mov     al,eDBSBoundCross       ; Y: boundary crossed error code
        jmp     short DLDRexit

DLDRsizeDMAbuff:
        cmp     ecx,[DMABufferSize]     ;Q: Smaller than DMA buffer
        jbe     short DLDRcheckDMAbuff  ; Y: check DMA buffer status
        mov     al,eDBSBufferSize       ; N: can't use DMA buffer due to size
        jmp     short DLDRexit          ;    being too small

DLDRcheckDMAbuff:
        bts     [DMAFlags],fDMABufferBusyBit ;Q: Is the DMA buffer in use?
        jnc     short DLDRallocDMAbuff       ; N: allocate it
        mov     al,eDBSBufferBusy            ; Y: return DMA buffer busy status
        jmp     short DLDRexit

DLDRallocDMAbuff:
        mov     es:[ebx].DDSSize,ecx    ; restore original buffer size
        mov     edx,edi                 ; restore flags

        test    edx,fDBSBufferCopy      ;Q: Need to copy data to DMA buffer?
        jz      short DLDRuseDMAbuff    ; N: use DMA buffer (get address)
        call    GetUserBufferPtr        ; Y: get source address and size
        mov     edi,[DMABufferAddress]  ;    get destination
        call    MoveBuffer

DLDRuseDMAbuff:
        call    GetBufferID             ; get a Buffer ID
        mov     es:[ebx].DDSBuffID,ax   ; fill in DMA buffer ID
        mov     eax,[DMABufferAddress]  ; DMA buffer physical address

DLDRok:
        mov     es:[ebx].DDSPhyAddr,eax ; fill in physical address in DDS
        xor     al,al                   ; no error

DLDRexit:
        ret

DBSLockDMARegion        endp
;==============================================================================
;==
;==  DBSUnlockDMARegion - Unlocks previously locked buffer.
;==
;==  Entry: (Protected Mode Ring 0)
;==     AL = 4
;==     DX = Flags: bit
;==                 1 = copy from buffer
;==     ES:(E)DI = pointer to DDS
;==
;==  Exit:  (Protected Mode Ring 0)
;==     AL = status value
;==     CY clear:   0h = OK
;==     CY set:    08h = memory was not locked
;==                0Ah = invalid buffer ID
;==                10h = illegal flags requested
;==
;==============================================================================
fDBSLegal=fDBSBufferCopy
DBSUnlockDMARegion proc near

        test    dx,not fDBSLegal        ;Q: Illegal flags used?
        jz      short DUDRptr           ; N: continue
        mov     al,eDBSInvalidFlag      ; Y: error return
        jmp     short DUDRexit

DUDRptr:
        movzx   edi,di                  ; convert to 32 bit pointer
        call    GetDDSPtr               ; convert V86 ES:EDI to protected ES:EBX
        mov     edi,[DMABufferAddress]  ; get address of DMA buffer

        cmp     es:[ebx].DDSPhyAddr,edi ;Q: DMA buffer address?
        je      short DUDRbuffID        ; Y: check for DMA buffer ID

        test    dx,fDBSBufferCopy       ;Q: Buffer copy flag?
        jnz     short DUDRInvBuffID     ; Y: error, invalid buffer ID

        cmp     es:[ebx].DDSBuffID,0    ;Q: Non-DMA buffer ID?
        je      short DUDRok            ; Y: unlock region
DUDRInvBuffID:
        mov     al,eDBSInvalidBuff      ; N: invalid buffer ID
        jmp     short DUDRexit

DUDRbuffID:
        cmp     es:[ebx].DDSBuffID,0    ;Q: Non-DMA buffer ID?  DWR
        je      short DUDRok            ; Y: unlock region      DWR
        mov     ax,es:[ebx].DDSBuffID
        call    ValidateBufferID        ;Q: DMA buffer ID?
        je      short DUDRcheck         ; Y: check if it is busy?
        mov     al,eDBSInvalidBuff      ; N: invalid buffer ID
        jmp     short DUDRexit

DUDRcheck:
        test    [DMAFlags],fDMABufferBusy ;Q: Is DMA buffer currently busy?
        jnz     short DUDRcont            ; Y: unlock it
        mov     al,eDBSLockRegion         ; N: DMA buffer was never locked
        jmp     short DUDRexit

DUDRcont:
        test    dx,fDBSBufferCopy       ;Q: Need to copy data from buffer?
        jz      short DUDRunlock        ; N: unlock DMA buffer

        call    GetUserBufferPtr        ; ESI/ECX is user buffer ptr/size
        xchg    esi,edi                 ; DMA buffer -> user buffer

        call    MoveBuffer

DUDRunlock:
        and     [DMAFlags],not fDMABufferBusy ; DMA buffer no longer in use

DUDRok:
        xor     al,al                   ; success

DUDRexit:
        ret

DBSUnlockDMARegion      endp
;==============================================================================
;==
;==  DBSScatGathLock - Attempts to lock a scatter/gather region.
;==
;==  Entry: (Protected Mode Ring 0)
;==     AL = 5
;==     DX = Flags: bit
;==                 6 = PTE's should be used
;==                 7 = not present pages should not be locked
;==     ES:(E)DI = pointer to extended DDS
;==
;==  Exit:  (Protected Mode Ring 0)
;==     AL = status value
;==     BX = offset into first PTE for region (only if bit 6 of flags is set).
;==     CY clear:   0 = OK
;==     CY set:     3 = unable to lock pages
;==                 9 = # scatter/gather regions is greater than table size
;==                10h= illegal flags requested
;==
;==============================================================================
fDBSLegal=fDBSPTEs+fDBSNotPresent
DBSScatGathLock proc    near
        push    fs

        test    dx,not fDBSLegal        ;Q: Illegal flags used?
        jz      short DSGLptr           ; N: continue
        mov     al,eDBSInvalidFlag      ; Y: error return
        xor     si,si                   ; no available entries used
        jmp     DSGLexit

DSGLptr:
        movzx   edi,di                  ; convert to 32 bit pointer
        call    GetDDSPtr               ; convert V86 ES:EDI to protected ES:EBX
        movzx   edi,es:[ebx].DDSEAvail  ; available entries

        call    GetUserBufferPtr        ; ESI/ECX is user buffer ptr/size
        push    es:[ebx].DDSsize        ; save size field for succesfull return
        mov     es:[ebx].DDSsize,0      ; clear size field in case of error
        mov     es:[ebx].DDSEUsed,0     ; used entries

        test    edx,fDBSPTEs            ;Q: Array of PTE's?
        jnz     short DSGLPTEs          ; Y: list PTE entries
;
;  Each entry in the array is composed of a physical address and a size for
;  which it is contiguous.  Each are DWORD fields.
;
        mov     eax,esi                 ; user buffer linear address
        xor     edx,edx                 ; no boundary restrictions
        xor     esi,esi

DSGLnextRegion:
        push    ecx                     ; save size to transfer
        push    eax                     ; save linear address

        call    ContigCheck             ; get contiguous buffer

        cmp     esi,edi                 ;Q: Any entries left?
        jae     short DSGLRegCont       ; N: don't record information

        mov     es:[ebx][DDSElist][esi*8].DDSERPhyAddr,eax ; save address
        mov     es:[ebx][DDSElist][esi*8].DDSERsize,ecx    ; save size
        add     es:[ebx].DDSsize,ecx    ; amount of buffer described by entries

DSGLRegCont:
        inc     esi                     ; next region
        pop     eax
        add     eax,ecx                 ; new linear address

        sub     [esp],ecx               ;Q: Still need more?
        pop     ecx                     ; amount still needed
        jnz     short DSGLnextRegion    ; Y: get next region
        jmp     short DSGLcomplete      ; N: all regions completed

;
;  Each entry in the array is composed of a PTE.
;
DSGLPTEs:
        mov     ax,PAGET_GSEL           ; access page tables
        mov     fs,ax
        xor     eax,eax

        xchg    eax,esi
        add     ecx,eax
        or      ecx,ecx                 ;Q: End of buffer @ 0?
        jz      short DSGLcont          ; Y: don't wrap on 4GB?
        dec     ecx                     ; N: last addressable byte in buffer
DSGLcont:
        push    eax                     ; save starting address

        shr     eax,12                  ; starting PTE index
        shr     ecx,12                  ; ending PTE index

DSGLloop:
        cmp     esi,edi                 ;Q: Any entries left?
        jae     short DSGLPTEcont       ; N: don't record information

        mov     edx,eax                 ; assume linear and physical are the same
        shl     edx,12
        cmp     eax,[MaxPTEIndex]       ;Q: Above the page tables?
        jae     short DSGLphys          ; Y: linear and physical are the same
        mov     edx,fs:[eax*4]          ; N: get PTE
DSGLphys:
        and     dx,0F000h
        or      dx,1                    ; page present and locked bit
        mov     es:[ebx][DDSElist][esi*4].DDSEP,edx ; save PTE
        add     es:[ebx].DDSsize,1000h  ; amount of buffer described by entries

DSGLPTEcont:
        inc     eax
        inc     esi
        cmp     eax,ecx                 ;Q: Need any more PTEs
        jbe     short DSGLloop          ; Y: get next PTE

        pop     eax                     ; restore starting buffer address
        and     eax,0FFFh               ; clear upper 20 bits

        mov     edx,[ebp][-4]                ; pointer to PUSHAD image
        mov     word ptr ss:[edx].reg_EBX,ax ; offset of buffer in first PTE

        or      edi,edi                 ;Q: Any entries available?
        jz      short DSGLcomplete      ; N: don't update size field
        sub     es:[ebx].DDSsize,eax    ; Y: reduce by offset into first PTE

DSGLcomplete:
        pop     ecx                     ; size field for succesfull return
        mov     es:[ebx].DDSEUsed,si    ; number of entries required
        mov     al,eDBSTableSize        ; assume need more space error code

        cmp     esi,edi                 ;Q: More entries required than available?
        ja      short DSGLexit          ; Y: need more space error
        xor     al,al                   ; N: no error
        mov     es:[ebx].DDSsize,ecx    ; restore original size field: succesfull return

DSGLexit:
        pop     fs
        ret

DBSScatGathLock endp
;==============================================================================
;==
;==  DBSScatGathUnlock - Attempts to unlock a scatter/gather region.
;==
;==  Entry: (Protected Mode Ring 0)
;==     AL = 6
;==     DX = Flags: bit
;==                 6 = PTE's should be used
;==                 7 = not present pages should not be locked
;==     ES:(E)DI = pointer to extended DDS
;==
;==  Exit:  (Protected Mode Ring 0)
;==     AL = status value
;==     CY clear:   0 = OK
;==     CY set:     7 = invalid memory region
;==                 8 = memory was not locked
;==                10h= illegal flags requested
;==
;==============================================================================
fDBSLegal=fDBSPTEs+fDBSNotPresent
DBSScatGathUnlock proc  near

        test    dx,not fDBSLegal        ;Q: Illegal flags used?
        jnz     short DSGUerror         ; Y: error
        xor     al,al                   ; N: buffer unlocked
        ret

DSGUerror:
        mov     al,eDBSInvalidFlag      ; error return
        ret
DBSScatGathUnlock       endp
;==============================================================================
;==
;==  DBSReqDMABuffer - Request a DMA Buffer
;==
;==  Entry: (Protected Mode Ring 0)
;==     AL = 7
;==     DX = Flags: bit
;==                 1 = copy to buffer
;==     ES:(E)DI = pointer to DDS
;==
;==  Exit:  (Protected Mode Ring 0)
;==     AL = status value
;==     CY clear:   0 = OK
;==     CY set:     4 = no buffer available
;==                 5 = DMA buffer is too small
;==                 6 = DMA buffer currently in use
;==                10h= illegal flags requested
;==
;==============================================================================
fDBSLegal=fDBSBufferCopy
DBSReqDMABuffer proc    near

        test    dx,not fDBSLegal        ;Q: Illegal flags used?
        jz      short DRqDBptr          ; N: continue
        mov     al,eDBSInvalidFlag      ; Y: error return
        jmp     short DRqDBexit

DRqDBptr:
        movzx   edi,di                    ; convert to 32 bit pointer
        test    [DMAFlags],fDMABufferBusy ;Q: Is DMA buffer currently busy?
        jz      short DRqDBbuffer         ; N: get user buffer pointer
        mov     al,eDBSBufferBusy         ; Y: DMA buffer is busy
        jmp     short DRqDBexit

DRqDBbuffer:
        call    GetDDSPtr               ; convert V86 ES:EDI to protected ES:EBX

        mov     ecx,[DMABufferSize]
        cmp     ecx,es:[ebx].DDSsize    ;Q: Is the DMA buffer large enough?
        jae     short DRqDBUseBuffer    ; Y: use the DMA buffer
        mov     al,eDBSBufferSize       ; N: DMA buffer is too small
        jmp     short DRqDBexit

DRqDBUseBuffer:
        or      [DMAFlags],fDMABufferBusy ; mark DMA buffer in use
        call    GetBufferID               ; get a unique buffer ID
        mov     es:[ebx].DDSBuffID,ax     ; DMA buffer ID
;QLEO   mov     es:[ebx].DDSsize,ecx      ; specify the size of the DMA buffer

        mov     edi,[DMABufferAddress]  ; get address of DMA buffer
        mov     es:[ebx].DDSPhyAddr,edi ; put in DDS

        test    dx,fDBSBufferCopy       ;Q: Need to copy data to DMA buffer?
        jz      short DRqDBok           ; N: return

        call    GetUserBufferPtr        ; ESI/ECX is user buffer ptr/size

        call    MoveBuffer

DRqDBok:
        xor     al,al                   ; success

DRqDBexit:
        ret

DBSReqDMABuffer endp
;==============================================================================
;==
;==  DBSRelDMABuffer - Release DMA Buffer
;==
;==  Entry: (Protected Mode Ring 0)
;==     AL = 8
;==     DX = Flags: bit
;==                 0 = 32 bit addressing mode used (ES:EDI)
;==                 1 = copy from buffer
;==     ES:(E)DI = pointer to DDS
;==
;==  Exit:  (Protected Mode Ring 0)
;==     AL = status value
;==     CY clear:   00h = OK
;==     CY set:     0Ah = invalid buffer ID
;==                 10h = illegal flags requested
;==
;==============================================================================
fDBSLegal=fDBSBufferCopy
DBSRelDMABuffer proc    near

        test    dx,not fDBSLegal        ;Q: Illegal flags used?
        jz      short DRDBptr           ; N: continue
        mov     al,eDBSInvalidFlag      ; Y: error return
        jmp     short DRDBexit

DRDBptr:
        movzx   edi,di                  ; convert to 32 bit pointer
        call    GetDDSPtr               ; convert V86 ES:EDI to protected ES:EBX
        mov     edi,[DMABufferAddress]  ; get address of DMA buffer

        mov     ax,es:[ebx].DDSBuffID
        call    ValidateBufferID        ;Q: DMA buffer ID?
        jne     short DRDBerror         ; N: invalid buffer ID

        test    [DMAFlags],fDMABufferBusy ;Q: Is DMA buffer currently busy?
        jz      short DRDBerror           ; N: invalid buffer ID

        test    dx,fDBSBufferCopy       ;Q: Need to copy data from buffer?
        jz      short DRDBunlock        ; N: unlock DMA buffer

        call    GetUserBufferPtr        ; ESI/ECX is user buffer ptr/size
        xchg    esi,edi                 ; DMA buffer -> user buffer

        call    MoveBuffer

DRDBunlock:
        and     [DMAFlags],not fDMABufferBusy ; DMA buffer no longer in use

DRDBok:
        xor     al,al                   ; success

DRDBexit:
        ret

DRDBerror:
        mov     al,eDBSInvalidBuff      ; DMA buffer was never locked
        ret

DBSRelDMABuffer endp
;==============================================================================
;==
;==  DBSCopyToBuffer - Copy data to DMA buffer
;==
;==  Entry: (Protected Mode Ring 0)
;==     AL = 9
;==     DX = Flags: bit (must be zero)
;==     ES:(E)DI  = pointer to DDS
;==     BX:CX/ECX = starting offset into DMA buffer
;==
;==  Exit:  (Protected Mode Ring 0)
;==     AL = status value
;==     CY clear:   00h = OK
;==     CY set:     0Ah = invalid buffer ID
;==                 0Bh = copy count + offset is larger than DMA buffer size
;==                 10h = illegal flags requested
;==
;==============================================================================
fDBSLegal=0
DBSCopyToBuffer proc    near

        test    dx,not fDBSLegal        ;Q: Illegal flags used?
        jz      short DCTBptr           ; N: continue
        mov     al,eDBSInvalidFlag      ; Y: error return
        jmp     short DCTBexit

DCTBptr:
        movzx   edi,di                  ; convert to 32 bit pointer
        shl     ebx,16                  ; convert BX:CX -> ECX
        mov     bx,cx
        mov     ecx,ebx                 ; offset into DMA buffer

        call    GetDDSPtr               ; convert V86 ES:EDI to protected ES:EBX
        mov     edi,[DMABufferAddress]  ; get address of DMA buffer
        add     edi,ecx                 ; starting transfer address

        mov     ax,es:[ebx].DDSBuffID
        call    ValidateBufferID        ;Q: DMA buffer ID?
        jne     short DCTBerrID         ; N: invalid buffer ID

        add     ecx,es:[ebx].DDSsize    ; size of DMA buffer needed for transfer
        cmp     [DMABufferSize],ecx     ;Q: DMA buffer large enough?
        jb      short DCTBerrCount      ; N: error

        call    GetUserBufferPtr        ; ESI/ECX is user buffer ptr/size

        call    MoveBuffer
        xor     al,al

DCTBexit:
        ret

DCTBerrID:
        mov     al,eDBSInvalidBuff      ; invalid buffer ID
        ret

DCTBerrCount:
        mov     al,eDBSBuffRange        ; copy range not within DMA buffer
        ret

DBSCopyToBuffer endp
;==============================================================================
;==
;==  DBSCopyFromBuffer - Copy data from DMA buffer
;==
;==  Entry: (Protected Mode Ring 0)
;==     AL = 10
;==     DX = Flags: bit (must be zero)
;==     ES:(E)DI  = pointer to DDS
;==     BX:CX/ECX = starting offset into DMA buffer
;==
;==  Exit:  (Protected Mode Ring 0)
;==     AL = status value
;==     CY clear:   00h = OK
;==     CY set:     0Ah = invalid buffer ID
;==                 0Bh = copy count + offset is larger than DMA buffer size
;==                 10h = illegal flags requested
;==
;==============================================================================
fDBSLegal=0
DBSCopyFromBuffer proc  near

        test    dx,not fDBSLegal        ;Q: Illegal flags used?
        jz      short DCFBptr           ; N: continue
        mov     al,eDBSInvalidFlag      ; Y: error return
        jmp     short DCFBexit

DCFBptr:
        movzx   edi,di                  ; N: convert to 32 bit pointer
        shl     ebx,16                  ; convert BX:CX -> ECX
        mov     bx,cx
        mov     ecx,ebx                 ; offset into DMA buffer

        call    GetDDSPtr               ; convert V86 ES:EDI to protected ES:EBX
        mov     edi,[DMABufferAddress]  ; get address of DMA buffer
        add     edi,ecx                 ; starting transfer address

        mov     ax,es:[ebx].DDSBuffID
        call    ValidateBufferID        ;Q: DMA buffer ID?
        jne     short DCFBerrID         ; N: invalid buffer ID

        add     ecx,es:[ebx].DDSsize    ; size of DMA buffer needed for transfer
        cmp     [DMABufferSize],ecx     ;Q: DMA buffer large enough?
        jb      short DCFBerrCount      ; N: error

        call    GetUserBufferPtr        ; ESI/ECX is user buffer ptr/size
        xchg    esi,edi                 ; DMA buffer -> user buffer

        call    MoveBuffer
        xor     al,al

DCFBexit:
        ret

DCFBerrID:
        mov     al,eDBSInvalidBuff      ; invalid buffer ID
        ret

DCFBerrCount:
        mov     al,eDBSBuffRange        ; copy range not within DMA buffer
        ret
DBSCopyFromBuffer       endp
;==============================================================================
;==
;==  DBSDisDMATrans - Disable DMA Translation
;==
;==  Entry: (Protected Mode Ring 0)
;==     AL = 11
;==     BX = DMA channel number
;==     DX = 0
;==
;==  Exit:  (Protected Mode Ring 0)
;==     AL = status value
;==     CY clear:   00h = OK
;==     CY set:     0Ch = invalid DMA channel number
;==                 0Dh = disable count overflow
;==                 10h = illegal flags requested
;==
;==============================================================================
fDBSLegal=0
DBSDisDMATrans  proc    near

        test    dx,not fDBSLegal        ;Q: Illegal flags used?
        jz      short DDDTptr           ; N: continue
        mov     al,eDBSInvalidFlag      ; Y: error return
        jmp     short DDDTexit

DDDTptr:
        cmp     bx,8                    ;Q: Valid DMA channel?
        jae     short DDDTerrChannel    ; N: error

        add     DBSDisTable[bx],1       ;Q: Disable count overflow?
        jc      short DDDTerrOver       ; Y: error
        xor     al,al                   ; N: successful status

        cmp     DBSDisTable[bx],1       ;Q: Was disable count = 0 ?
        jne     SHORT DDDTexit          ; N: exit, disable flag already set

        mov     cx,bx
        xor     bx,bx
        jcxz    short DDDTRegRec

DDDTloop:
        add     bx,size DMARegRec
        loop    DDDTloop

DDDTRegRec:
        or      [DMARegSav][bx].DMAChnFlgs,fNoTrans ; set disable trans flag

DDDTexit:
        ret


DDDTerrOver:
        mov     DBSDisTable[bx],-1      ; maintain translation disabled
        mov     al,eDBSCountOver        ; overflow
        ret

DDDTerrChannel:
        mov     al,eDBSInvalidDMA       ; invalid DMA channel
        ret

DBSDisDMATrans  endp
;==============================================================================
;==
;==  DBSEnaDMATrans - Enable DMA Translation
;==
;==  Entry: (Protected Mode Ring 0)
;==     AL = 12
;==     BX = DMA channel number
;==     DX = 0
;==
;==  Exit:  (Protected Mode Ring 0)
;==     AL = status value
;==     CY clear:   00h = OK (ZF, if count is zero, else NZ)
;==     CY set:     0Ch = invalid DMA channel number
;==                 0Dh = disable count underflow
;==                 10h = illegal flags requested
;==
;==============================================================================
fDBSLegal=0
DBSEnaDMATrans  proc    near

        test    dx,not fDBSLegal        ;Q: Illegal flags used?
        jz      short DEDTptr           ; N: continue
        mov     al,eDBSInvalidFlag      ; Y: error return
        jmp     short DEDTexit

DEDTptr:
        cmp     bx,8                    ;Q: Valid DMA channel?
        jae     short DEDTerrChannel    ; N: error

        and     [ebp][VTFO].VMTF_EFLAGS,not FLAGS_ZF ; assume count not zero

        sub     DBSDisTable[bx],1       ;Q: Disable translation?
        jc      short DEDTerrUnder      ; -: error, underflow
        jne     short DEDTok            ; N: just decrement disable count

        or      [ebp][VTFO].VMTF_EFLAGS,FLAGS_ZF ; count is zero

        mov     cx,bx
        xor     bx,bx
        jcxz    short DEDTRegRec

DEDTloop:
        add     bx,size DMARegRec
        loop    DEDTloop

DEDTRegRec:
        and     [DMARegSav][bx].DMAChnFlgs,not fNoTrans ; translation enabled

DEDTok:
        xor     al,al

DEDTexit:
        ret

DEDTerrUnder:
        mov     DBSDisTable[bx],0       ; maintain translation enabled
        mov     al,eDBSCountUnder       ; overflow
        ret

DEDTerrChannel:
        mov     al,eDBSInvalidDMA       ; invalid DMA channel
        ret

DBSEnaDMATrans  endp
;==============================================================================
;==
;==  GetBufferID - Returns a unique buffer ID in AX.
;==
;==  Entry: (Protected Mode Ring 0)
;==
;==  Exit:  (Protected Mode Ring 0)
;==     AX = Unique Buffer ID
;==
;==============================================================================
GetBufferID     proc    near
        mov     ax,[DMABuffID]          ; get previous DMA ID
        inc     ax                      ;Q: ID of Zero?
        jnz     short GBIDcont          ; N: OK, current ID
        inc     ax                      ; Y: make current ID a one.
GBIDcont:
        mov     [DMABuffID],ax          ; save current ID
        ret
GetBufferID     endp
;==============================================================================
;==
;==  ValidateBufferID - Verifies a valid buffer ID in AX. (This routine is
;==                     provided for easy expandability to multiple DMA
;==                     buffers)
;==
;==  Entry: (Protected Mode Ring 0)
;==     AX = Proposed buffer ID
;==
;==  Exit:  (Protected Mode Ring 0)
;==     ZR = Buffer ID is valid
;==     NZ = Buffer ID is invalid
;==
;==============================================================================
ValidateBufferID proc   near
        cmp     ax,[DMABuffID]          ;Q: Valid buffer ID? (CY- if invalid)
        ret
ValidateBufferID        endp
;==============================================================================
;==
;==  GetDDSPtr - Returns ES:EBX pointing to DDS.
;==
;==  Entry: (Protected Mode Ring 0)
;==     EDI = offset into virtual ES segment
;==     EBP = base pointer for virtual stack frame
;==
;==  Exit:  (Protected Mode Ring 0)
;==     EBX = pointer to DDS
;==
;==============================================================================
GetDDSPtr proc  near
        movzx   ebx,[ebp].VTFO+VMTF_ES  ; get virtual mode segment
        shl     ebx,4
        add     ebx,edi                 ; DDS pointer
        ret
GetDDSPtr       endp
;==============================================================================
;==
;==  GetUserBufferPtr - Returns ES:ESI pointing to user buffer.
;==
;==  Entry: (Protected Mode Ring 0)
;==     EBX = pointer to DDS
;==
;==  Exit:  (Protected Mode Ring 0)
;==     ESI = pointer to user buffer
;==     ECX = size of user buffer
;==
;==============================================================================
GetUserBufferPtr proc   near
        movzx   esi,es:[ebx].DDSseg     ; get segment for buffer
        shl     esi,4
        add     esi,es:[ebx].DDSLinOffs ; user buffer pointer

        mov     ecx,es:[ebx].DDSsize    ; user buffer size
        ret
GetUserBufferPtr        endp
;==============================================================================
;==
;==  pINT4BhHandler: DMA/bus master virtual 8086 mode services
;==
;==  Entry: (Protected Mode)
;==
;==  Exit:
;==
;==============================================================================
pINT4BhHandler:
        push    ebp
        movzx   ebp,sp
        sub     esp,4           ; used to store PUSHAD pointer in dispatcher

        push    VDMD_GSEL       ; DS/GS are setup for CEMM's 2 data areas.
        pop     ds
        push    RCODEA_GSEL
        pop     gs
        ASSUME  ds:_DATA,gs:R_CODE

        btr     gs:[TrapFlags],fI4BtrapBit ;Q: Reflect back?
        jnc     short pI4Breflect          ; Y: reflect interrupt

        call    DBSdispatcher           ; dispatch function

        add     esp,4
        pop     ebp
        iretd

pI4Breflect:
        add     esp,4
        push    4Bh                     ; reflect interrupt into virtual mode
        jmp     ReflectInterrupt

_TEXT   ends                            ; end of segment


        end                             ; end of module
