windows-nt/Source/XPSP1/NT/base/mvdm/dpmi/dxdma.asm
2020-09-26 16:20:57 +08:00

723 lines
22 KiB
NASM

PAGE ,132
TITLE DXDMA.ASM -- Dos Extender DMA Services
; Copyright (c) Microsoft Corporation 1989-1991. All Rights Reserved.
;***********************************************************************
;
; DXDMA.ASM -- Dos Extender DMA Services
;
;-----------------------------------------------------------------------
;
; This module provides the protect mode DMA services for the 286 DOS
; Extender. It supports a subset of the services documented in
; "DMA Services for DOS Virtual 8086 and Protected Mode Environments"
; by Microsoft Corporation.
;
;-----------------------------------------------------------------------
;
; 12/06/89 jimmat Minor changes to reflect updates in DMA Service Spec.
; 11/01/89 jimmat Started.
;
;***********************************************************************
.286p
; -------------------------------------------------------
; INCLUDE FILE DEFINITIONS
; -------------------------------------------------------
.xlist
.sall
include segdefs.inc
include gendefs.inc
include pmdefs.inc
include interupt.inc
if VCPI
include dxvcpi.inc
endif
.list
; -------------------------------------------------------
; GENERAL SYMBOL DEFINITIONS
; -------------------------------------------------------
PhysicalPageSize equ 4096 ;size of 80386 physical page
PageShift equ 12
DMAServiceByte equ 7Bh
ChainReserved equ 08h ;set if unsupported services chained
DMAServiceID equ 81h ;Int 4Bh/AH=81h are the DMA services
FirstValidSvc equ 02h ;First valid DMA service #
LastValidSvc equ 0Ch ;Last valid DMA service #
; Get Version information
MajorVersion equ 01h ;Major specification version
MinorVersion equ 00h ;Minor specification version
DOSXProductNumber equ 02h ;286 DOS Extender product number
DOSXProductRevision equ 01h ;286 DOS Extender revision number
MemoryContiguous equ 08h ;All memory physically contigious flag
AutoRemapSupported equ 04h ;Automatic remap supported
; DX flags on calls to DMA services
AutoBufferCopy equ 02h ;set if data to be copied into DMA buf
NoAutoBufferAlloc equ 04h ;set if NO automatic buff allocation
NoAutoRemap equ 08h ;set if NO automatic remap attempted
Align64k equ 10h ;set if region can't cross 64k boundry
Align128k equ 20h ;set if region can't cross 128k boundry
PageTableFmt equ 40h ;set for page table Scatter/Gather lock
ValidDXFlags equ 007Eh ;valid DX register flag bits (above)
; Error return codes
ErrRegionCrossedBoundry equ 02h
ErrNoBufferAvail equ 04h
ErrTooManyRegions equ 09h
ErrInvalidBufferID equ 0Ah
ErrFuncNotSupported equ 0Fh
ErrReservedFlagBits equ 10h
; DMA Descriptor Structure(s)
DDS STRUC ;normal DDS
DDS_RegionSize dd ?
DDS_Offset dd ?
DDS_Selector dw ?
DDS_BufferID dw ?
DDS_PhyAddress dd ?
DDS ENDS
SGDDS1 STRUC ;Extended DDS for Scatter/Gather
dd 3 dup (?) ; Region format
DDS_NumAvail dw ?
DDS_NumUsed dw ?
DDS_Region0Addr dd ?
DDS_Region0Size dd ?
SGDDS1 ENDS
SGDDS2 STRUC ;Extended DDS for Scatter/Gather
dd 4 dup (?) ; Page Table format
DDS_PageTblEnt0 dd ?
SGDDS2 ENDS
; -------------------------------------------------------
; EXTERNAL SYMBOL DEFINITIONS
; -------------------------------------------------------
extrn EnterIntHandler:NEAR
extrn LeaveIntHandler:NEAR
extrn GetSegmentAddress:NEAR
extrn PMIntrEntryVector:NEAR
; -------------------------------------------------------
; DATA SEGMENT DEFINITIONS
; -------------------------------------------------------
DXDATA segment
ifdef NEC_98
extrn fPCH98:BYTE
else ;!NEC_98
ifdef NOT_NTVDM_NOT
extrn fMicroChannel:BYTE
endif
endif ;!NEC_98
if VCPI
extrn fVCPI:BYTE
endif
;
; Contains a copy of bit 5 of the byte at 40:7b, that indicates
; whether VDS should be used.
;
public bDMAServiceBit
bDMAServiceBit db 0
DXDATA ends
; -------------------------------------------------------
; CODE SEGMENT VARIABLES
; -------------------------------------------------------
DXCODE segment
DXCODE ends
DXPMCODE segment
extrn selDgroupPM:WORD
DMASvcTbl label word
dw offset DXPMCODE:GetVersion
dw offset DXPMCODE:LockDMARegion
dw offset DXPMCODE:DoNothing ;UnlockDMARegion
dw offset DXPMCODE:ScatterGatherLock
dw offset DXPMCODE:DoNothing ;ScatterGatherUnlock
dw offset DXPMCODE:Fail4NoBuffer ;RequestDMABuffer
dw offset DXPMCODE:DoNothing ;ReleaseDMABuffer
dw offset DXPMCODE:Fail4BufferID ;CopyIntoDMABuffer
dw offset DXPMCODE:Fail4BufferID ;CopyOutOfDMABuffer
dw offset DXPMCODE:DoNothing ;DisableDMATranslation
dw offset DXPMCODE:DoNothing ;EnableDMATranalation
DXPMCODE ends
; -------------------------------------------------------
subttl DMA Service Dispatcher
page
; -------------------------------------------------------
; DMA SERVICE DISPATCHER
; -------------------------------------------------------
DXPMCODE segment
assume cs:DXPMCODE
; -------------------------------------------------------
; PMIntr4B -- Entry routine/dispatcher for protected mode DMA
; services. The DMA services are invoked with an Int 4Bh
; interrupt. The 286 DOS Extender only supports the DMA
; services in protected mode. Other systems that use Virtual
; 8086 mode on 386 processors will most likely need to support
; the services in virtual mode also.
;
; The following services are supported (Int 4Bh/AH = 81h):
;
; AL = 00 Reserved
; 01 Reserved
; 02 Get Version
; 03 Lock DMA Region
; 04 Unlock DMA Region
; 05 Scatter/Gather Lock Region
; 06 Scatter/Gather Unlock Region
; 07 Request DMA Buffer
; 08 Release DMA Buffer
; 09 Copy Into DMA Buffer
; 0A Copy Out Of DMA Buffer
; 0B Disable DMA Translation
; 0C Enable DMA Translation
; 0D Reserved
; ...
; FF Reserved
public PMIntr4B
assume ds:NOTHING,es:NOTHING,ss:NOTHING
PMIntr4B proc near
; Is this one of the supported DMA services?
cmp ah,DMAServiceID ;is this a DMA service request?
jz @f
jmp short i4b_other_service ; no, see if it should be chained
@@:
call EnterIntHandler ;saves regs, switches stacks, etc.
assume ds:DGROUP ;sets DS/ES = DGROUP
mov es,[bp].pmUserES ; but we want caller's ES
cld ;cya...
cmp al,FirstValidSvc
jb i4b_reserved
cmp al,LastValidSvc
ja i4b_reserved
test dx,NOT ValidDXFlags ;any reserved flags set?
jnz i4b_bad_flags
; -------------------------------------------------------
; Setup local environment and dispatch the service
i4b_valid_svc:
push offset DXPMCODE:i4b_svc_ret ;save dispatch return address
sub al,FirstValidSvc ;get address of service routine
cbw
shl ax,1
add ax,offset DXPMCODE:DMASvcTbl
xchg ax,bx
mov bx,cs:[bx]
xchg ax,bx
push ax ;save routine address on stack
mov ax,[bp].pmUserAX ;restore entry AX
ret ;invoke service routine
i4b_svc_ret: ;service routines return here
jnc i4b_good_return ;CY set if service failed
i4b_error_return:
or byte ptr [bp].intUserFL,1 ;set CY in caller's flags
jmp short i4b_exit
i4b_good_return:
and byte ptr [bp].intUserFL,not 1 ;clear CY in caller's flags
i4b_exit:
call LeaveIntHandler ;resotre stack, regs, etc.
iret
; -------------------------------------------------------
; Reserved DMA service 00, 01, 0D-FF; return with CY set and
; AL = ErrFuncNotSupported
i4b_reserved:
mov byte ptr [bp].intUserAX,ErrFuncNotSupported
jmp short i4b_error_return
; -------------------------------------------------------
; User made a DMA service call with a reserved flag bit set. Fail the
; call with AL = ErrReservedFlagBits
i4b_bad_flags:
mov byte ptr [bp].intUserAX,ErrReservedFlagBits
jmp short i4b_error_return
; -------------------------------------------------------
; This is a non-DMA Int 4B call. On Micro Channel systems, bit 3
; in location 40:007B indicates if we should chain the call along
; or not. If the bit is set, we chain. If not Micro Channel, or the
; bit is not set, we check the real mode Int 4Bh vector to see if someone
; other than the BIOS has it hooked--if so, we chain anyway. If not,
; return without changing any regs or flags.
i4b_other_service:
push ds
mov ds,selDgroupPM
assume ds:DGROUP
ifdef NOT_NTVDM_NOT
ifdef NEC_98
test fPCH98,0FFh
else ;!NEC_98
test fMicroChannel,0FFh ;if micro channel system
endif ;!NEC_98
jz i4b_check_vector ; and 40:7B bit 3 set,
; chain the call to real mode
push SEL_BIOSDATA or STD_RING
pop ds
assume ds:NOTHING
test byte ptr ds:DMAServiceByte,ChainReserved
jnz i4b_chain
endif
i4b_check_vector: ;not micro channel, or bit 3 not set
push ax ;check if the real mode Int 4Bh
mov ax,SEL_RMIVT or STD_RING ; points somewhere and not
mov ds,ax ; at the BIOS--if so, chain
mov ax,word ptr ds:[4Bh*4] ; anyway.
or ax,word ptr ds:[4Bh*4+2]
pop ax
jz i4b_dont_chain
cmp word ptr ds:[4bh*4+2],0E000h
jz i4b_dont_chain
cmp word ptr ds:[4bh*4+2],0F000h
jz i4b_dont_chain
i4b_chain:
pop ds ;chain the request to real mode
jmp PMIntrEntryVector + 5*4Bh ; (no one can have pMode
; hooked before us)
i4b_dont_chain: ;don't chain the interrupt,
; just return quietly
pop ds
iret
PMIntr4B endp
; -------------------------------------------------------
subttl DMA Service Routines
page
; -------------------------------------------------------
; DMA SERVICE ROUTINES
; -------------------------------------------------------
; -------------------------------------------------------
; RM4B -- Call the real mode INT 4Bh handler to
; perform a DMA service, most likely something to
; do with the VCPI provider's buffer when we are
; running under VCPI.
;
; Input: depends on call
; Output: Flags, registers from real mode call
;
; Notes: This is strictly an internal DOSX call,
; so if a long pointer is used, then it
; is assumed to point into DOSX's data
; segment.
;
cProc RM4B,<NEAR,PUBLIC>
cBegin
pushf
push cs
call near ptr PMIntrEntryVector + 3*4Bh
cEnd
; -------------------------------------------------------
; DoNothing -- This routine does nothing other than return
; indicating that the DMA service succeeded.
;
; Input: none
; Output: AL = 0, CY clear
assume ds:DGROUP,es:NOTHING,ss:NOTHING
DoNothing proc near
clc ;indicate success
ret
DoNothing endp
; -------------------------------------------------------
; Fail4BufferID -- This routine does nothing other than return
; indicating that the DMA service failed with 'Invalid Buffer ID'
;
; Input: none
; Output: AL = 0Ah, CY set
assume ds:DGROUP,es:NOTHING,ss:NOTHING
Fail4BufferID proc near
mov byte ptr [bp].intUserAX,ErrInvalidBufferID
stc
ret
Fail4BufferID endp
; -------------------------------------------------------
; Fail4NoBuffer -- This routine does nothing other than return
; indicating that the DMA service failed with 'No Buffer Available'
;
; Input: none
; Output: AL = 04h, CY set
assume ds:DGROUP,es:NOTHING,ss:NOTHING
Fail4NoBuffer proc near
mov byte ptr [bp].intUserAX,ErrNoBufferAvail
stc
ret
Fail4NoBuffer endp
; -------------------------------------------------------
; GetVersion -- This routine processes the DMA Get Version
; service (AL = 02).
;
; Input: none
; Output: AH/AL - Major/Minor specification level
; BX - Product number
; CX - Product revision number
; DX - flags
; SI:DI - 32 bit max buffer size available
assume ds:DGROUP,es:NOTHING,ss:NOTHING
GetVersion proc near
mov [bp].intUserAX,(MajorVersion shl 8) or MinorVersion
mov [bp].intUserBX,DOSXProductNumber
mov [bp].intUserCX,DOSXProductRevision
mov [bp].intUserDX,MemoryContiguous
xor ax,ax ;0 buffer size supported, also clears
mov [bp].intUserSI,ax ; carry flag
mov [bp].intUserDI,ax
if VCPI
cmp fVCPI,0
je gv_x
mov ax,8102h
xor dx,dx ;DX = 0
cmp bDMAServiceBit,0 ;VCPI provider supports VDS?
je gv_e
call RM4B
jc gv_x
mov [bp].intUserSI,si
mov [bp].intUserDI,di
and dx,NOT (AutoRemapSupported or MemoryContiguous)
gv_e:
mov [bp].intUserDX,dx
gv_x:
endif
ret
GetVersion endp
; -------------------------------------------------------
; LockDMARegion -- This routine processes the Lock DMA Region
; service (AL = 03).
;
; Input: DX - flags
; ES:DI - ptr to DDS
; Output: if successful, CY clear; else CY set and AL = error code
; DDS updated
assume ds:DGROUP,es:NOTHING,ss:NOTHING
LockDMARegion proc near
; Since we don't support paging or anything interesting like that, the only
; thing that can fail us is an alignment problem--check for that.
test dl,Align64k or Align128k ;if they don't care,
jz lock_ok ; we don't care
mov si,dx ;save flags in SI
call CalcDDSPhyAddress ;see where the region is
push dx ;save start address
push ax
mov bx,dx ;bx = hi word start addr
add ax,word ptr es:[di].DDS_RegionSize ;see where it ends
adc dx,word ptr es:[di].DDS_RegionSize+2
sub ax,1 ;less 1 to point at
sbb dx,0 ; last byte, not next
mov cx,dx ;cx = hi word end addr
test si,Align128k ;64k or 128k alignment wanted?
jz @f ; already setup for 64k
and bl,not 1 ;mask to 128k alignment
and cl,not 1
@@:
cmp bx,cx ;within the boundry?
jz lock_ok_clr_stk ; yes, 'lock' it
; The region crosses an alignment boundary, we need to update the allowed
; region size in the DDS, and fail the call.
pop cx
pop dx ;dx:cx = region start address
neg cx ;cx = len to next 64k boundry
xor bx,bx
test si,Align128k
jz @f
mov bl,dl
and bl,1
xor bl,1 ;bx:cx = len to next alignment boundry
@@:
mov word ptr es:[di].DDS_RegionSize,cx ;update size in DDS
mov word ptr es:[di].DDS_RegionSize+2,bx
mov byte ptr [bp].intUserAX,ErrRegionCrossedBoundry ;flag failure
stc
ret
lock_ok_clr_stk:
add sp,4 ;clear start address from stack
; No alignment problem, we can 'lock' the region.
lock_ok:
call CalcDDSPhyAddress ;get physical address of region DX:AX
mov word ptr es:[di].DDS_PhyAddress,ax
mov word ptr es:[di].DDS_PhyAddress+2,dx
xor ax,ax ;*** also clears CY! ***
mov es:[di].DDS_BufferID,ax ;no buffer used
ret
LockDMARegion endp
; -------------------------------------------------------
; ScatterGatterLock -- This routine implements the Scatter/Gather
; Lock Region DMA service (AL = 05h).
;
; Input: DX - flags
; ES:DI - ptr to extended DDS
; Output: if successful, CY clear; else CY set & AL = error code
; DDS updated
assume ds:DGROUP,es:NOTHING,ss:NOTHING
ScatterGatherLock proc near
test dl,PageTableFmt ;Scatter/Gather page table form?
jnz do_page_tbl_lock
; This is the region form of Scatter/Gather Lock Region -- for us this
; is easy, since memory is contiguous -- one region covers the entire area.
mov ax,1 ;we need one region entry
mov es:[di].DDS_NumUsed,ax
cmp es:[di].DDS_NumAvail,ax ; is it available?
jb not_enough_entries
call CalcDDSPhyAddress ;get physical address
mov word ptr es:[di].DDS_Region0Addr,ax ;store in extended DDS
mov word ptr es:[di].DDS_Region0Addr+2,dx
mov ax,word ptr es:[di].DDS_RegionSize ;copy over the size
mov word ptr es:[di].DDS_Region0Size,ax
mov ax,word ptr es:[di].DDS_RegionSize+2
mov word ptr es:[di].DDS_Region0Size+2,ax
clc ;indicate success
ret
; This is the page table form of Scatter/Gather Lock Region -- we need to
; build a fake page table (even though we may be on an 80286!?) to return
; in the extended DDS.
do_page_tbl_lock:
call CalcDDSPhyAddress ;get region start address
mov cx,word ptr es:[di].DDS_RegionSize ;calc # pages needed
mov bx,word ptr es:[di].DDS_RegionSize+2 ; for region of this
add cx,PhysicalPageSize-1 ; size
adc bx,0
shr cx,PageShift
shl bx,16-PageShift
or cx,bx ;cx = # pages
test ax,PhysicalPageSize-1 ;if region doesn't start on a page
jz @f ; boundry, add another page to
inc cx ; the region size
@@:
mov es:[di].DDS_NumUsed,cx ;tell caller how many used/needed
cmp es:[di].DDS_NumAvail,cx ;did caller supply enough page entries?
jb not_enough_entries ; no!
push ax ;save low word of region start address
and ax,NOT PhysicalPageSize-1 ;round down to page boundry
or al,1 ;set page present/locked bit
mov bx,di ;es:bx -> page table entries
jcxz page_ents_done ;better safe than sorry
@@:
mov word ptr es:[bx].DDS_PageTblEnt0,ax ;build fake page
mov word ptr es:[bx].DDS_PageTblEnt0+2,dx ; table entries...
add ax,PhysicalPageSize
adc dx,0
add bx,4
loop @b
page_ents_done:
pop ax ;recover low word of start address
and ax,PhysicalPageSize-1 ; and get offset into first page
mov [bp].intUserBX,ax ; return to caller in BX
clc ;indicate success
ret
; Fail the request for insufficient # of region/page tbl entries
not_enough_entries:
mov byte ptr [bp].intUserAX,ErrTooManyRegions ;AL = error code
mov ax,es:[di].DDS_NumAvail ;store max lockable
mov dx,ax ; size (bytes) in
shl ax,PageShift ; DDS region size
shr dx,16-PageShift
mov word ptr es:[di].DDS_RegionSize,ax
mov word ptr es:[di].DDS_RegionSize+2,dx
stc ;indicate failure
ret
ScatterGatherLock endp
; -------------------------------------------------------
subttl DMA Service Utility Routines
page
; -------------------------------------------------------
; DMA SERVICE UTILITY ROUTINES
; -------------------------------------------------------
; -------------------------------------------------------
; CalcDDSPhyAddress -- This routine calculates the physical
; address of the region specified in a DDS.
;
; Input: ES:DI - ptr to DDS
; Output: DX:AX - 32 bit physical address
; Uses: none.
assume ds:DGROUP,es:NOTHING,ss:NOTHING
CalcDDSPhyAddress proc near
push bx
xor bx,bx
mov dx,bx
mov ax,es:[di.DDS_Selector] ;if a selector is given,
or ax,ax ; get it's base address
jz @f
call GetSegmentAddress ;bx:dx = segment base
@@:
add dx,word ptr es:[di.DDS_Offset] ;add 32 bit offset
adc bx,word ptr es:[di.DDS_Offset+2]
mov ax,dx ;32 bit address to dx:ax
mov dx,bx
pop bx
ret
CalcDDSPhyAddress endp
DXPMCODE ends
;****************************************************************
end