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 NOT_NTVDM_NOT extrn fMicroChannel:BYTE endif 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 test fMicroChannel,0FFh ;if micro channel system 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 + 3*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, 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 -- silly, but easy enough... 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