                           -------------------------
                           |     DMA Tutorial      |
                           |     Revision 1.20     |
                           |     Aug 01, 1994      |
                           |    By Tom Marshall    |
                           | 1:3407/12.2 (Fidonet) |
                           -------------------------

INTRODUCTION
============
    I recently got into SoundBlaster programming, which requires DMA.
    Information on DMA programming is extremely scarce, and it seems that
    someone is always asking how to use it, so I decided to write a document
    on using the DMA.  None of the information in this document was obtained
    from "hard" sources (books, tech sheets, etc.)  Everything here was
    gleaned from other text files.  Some is tested, some is not.  Some
    information (especially on 16-bit channels) is extrapolated.  If a
    register or option is not explained fully, it is probably because the
    information was copied and I don't know myself.  Use this information at
    YOUR OWN RISK!  If you find any errors or typos, please let me know via
    the 80XXX echo or netmail.  I would like to thank the following people for
    their help in my quest for DMA information:

    James Vahn (1:346/25.3) - James keeps the 80XXX snippets, the source for
        most of this information.  If it weren't for him, I'd still be
        pestering people for information!
    Draeden of VLA (Unknown) - Draeden wrote a great article on DMA
        programming with sample code.  I hear he's put out some good stuff on
        graphics, too.
    Inbar Raz (2:403/123.5) - Inbar posted a message on 80XXX about two years
        listing all the DMA ports.  I filed it away for future use, and it
        came in very handy as a supplement to Draeden's article.
    Jim Roberts (1:301/40) - Jim wrote an article on SoundBlaster programming
        which covers DMA transfers and includes sample code.  His article also
        explains how to use the mysterious Auto-Init DMA mode on the SB.
    Coridon Henshaw (1:250/820) - Corrections and updates.
    Bruce Wedding (1:106/4708) - Corrections and updates.

    This document explains how to utilize the DMA channels on a standard AT
    class machine.  The AT maintains backward compatibility with the XT by
    using one 8-bit DMA controller and one 16-bit DMA controller.  The 8-bit
    channels are 0-3 and the 16-bit channels are 4-7.  Although no provision
    is made in this document to explain DMA on the XT, programming the DMA
    controller is similar between the two machines.  The same ports are used
    for the 8-bit channels; however the XT doesn't have any 16-bit channels.
    The XT also uses DMA channels for different functions than the AT:  the
    only free DMA channel on the XT is 1.  The following table illustrates
    each channel's usage on both machines (corrections, please?):

    Channel   Size      Usage in XT         Usage in AT
    -------  ------  ------------------  ------------------
       0      8-Bit  Memory refresh *    Free for use *
       1      8-Bit  Free for use *      Free for use *
       2      8-Bit  Floppy controller   Floppy controller
       3      8-Bit  HDD controller      Free for use
       4     16-Bit  N/A                 Cascade, not avail
       5     16-Bit  N/A                 Free for use
       6     16-Bit  N/A                 Free for use
       7     16-Bit  N/A                 Free for use

    * Also used for memory-to-memory transfers

DMA LIMITATIONS AND APPLICATIONS
================================
    DMA transfer speed is significantly slower than manual transfers.  There
    are several reasons for this, including backward compatibility with the XT
    and limited buss bandwidth.  One text file I read (author unknown) said
    that the theoretical maximum transfer speed is about 350k/sec on the 8-bit
    channels.  I would expect the 16-bit channels to transfer data twice as
    fast as the 8-bit channels, but that's still not very fast by today's
    standards.

    DMA transfers are programmed using a page, offset, and length.  These are
    analogous to the DS, SI, and CX registers when using REP MOVSB.  You don't
    need a destination address because the peripheral "sees" the data on the
    buss.  The page can be thought of as a DMA segment register.  It can be
    set to any valid value, but it cannot change during a transfer.  The
    offset and length are both 16 bits.  So there are two limits on DMA
    transfers. First, the block of memory that is transferred cannot overflow
    across pages.  For the 8-bit channels, the pages start at physical 64k
    intervals and each is 64k long.  For the 16-bit channels, the pages start
    at physical 64k intervals and each is 128k long (because the block size is
    specified in words).  Second, the size of the DMA transfer is limited to
    the length of the page (64k or 128k).

    With all these restrictions on DMA transfers, why use DMA at all?  It's
    quicker and easier to transfer information directly with the CPU.  The
    answer is that DMA transfers take place in the background, without CPU
    intervention.  While the DMA is transferring data, the CPU can continue
    processing other information.  Aside from standard usage (drive
    controllers), the most common use for DMA is transferring data to and from
    sound cards.  It can also be used to transfer blocks of information from
    one part of memory to another.  For example, DMA could enable you to send
    digitized sound to the sound card and update the graphics screen in the
    background while the CPU is calculating the next frame for the screen.


DMA REGISTERS
=============
    The DMA registers are accessed via I/O ports.  In order to maintain
    backward compatibility with the XT, the 16-bit registers are located in a
    separate address range from the 8-bit channels.  Since DMA is implemented
    on the motherboard, all DMA ports are in the range 00h-FFh.  The ports are
    arranged as follows:

        General Registers (8-bit)
        =========================
            Port  Access  Function
            ----  ------  --------
            08h   Rd/Wrt  Command and Status Register
            09h      Wrt  Request Register
            0Ah      Wrt  Single Mask Register
            0Bh      Wrt  Mode Register
            0Ch      Wrt  Clear Flip/Flop Register
            0Dh      Wrt  Master Reset Register
            0Eh      Wrt  Master Enable Register
            0Fh      Wrt  Master Mask Register

        General Registers (16-bit)
        ==========================
            D0h   Rd/Wrt  Command and Status Register
            D2h      Wrt  Request Register
            D4h      Wrt  Single Mask Register
            D6h      Wrt  Mode Register
            D8h      Wrt  Clear Flip/Flop Register
            DAh      Wrt  Master Reset Register
            DCh      Wrt  Master Enable Register
            DEh      Wrt  Master Mask Register

        Individual Channel Registers
        ============================
            Port  Access  Function
            ----  ------  --------
            00h   Wrt/Rd  Channel 0 Offset
            01h   Wrt/Rd  Channel 0 Block Size / Countdown
            02h   Wrt/Rd  Channel 1 Offset
            03h   Wrt/Rd  Channel 1 Block Size / Countdown
            04h   Wrt/Rd  Channel 2 Offset
            05h   Wrt/Rd  Channel 2 Block Size / Countdown
            06h   Wrt/Rd  Channel 3 Offset
            07h   Wrt/Rd  Channel 3 Block Size / Countdown
            87h   Wrt     Channel 0 Page Register
            83h   Wrt     Channel 1 Page Register
            81h   Wrt     Channel 2 Page Register
            82h   Wrt     Channel 3 Page Register

            C0h   Wrt/Rd  Channel 4 Offset
            C2h   Wrt/Rd  Channel 4 Block Size / Countdown
            C4h   Wrt/Rd  Channel 5 Offset
            C4h   Wrt/Rd  Channel 5 Block Size / Countdown
            C8h   Wrt/Rd  Channel 6 Offset
            C6h   Wrt/Rd  Channel 6 Block Size / Countdown
            CCh   Wrt/Rd  Channel 7 Offset
            CEh   Wrt/Rd  Channel 7 Block Size / Countdown
            8Fh   Wrt     Channel 4 Page Register
            8Bh   Wrt     Channel 5 Page Register
            89h   Wrt     Channel 6 Page Register
            8Ah   Wrt     Channel 7 Page Register

    As you can see, the registers are identical for both DMA controllers
    (8-bit channels 0-3 and 16-bit channels 4-7).  Each register is described
    once in the following list.  Some functions may not apply to the 16-bit
    controller, (which? is 16bit mem-mem allowed?).  As I stated earlier, I
    don't have a lot of information on the 16-bit channels.  When selecting a
    channel in binary for the 16-bit controller, subtract 4 from the channel
    number.  For example, channel 5 would be 01b.  You MUST disable a channel
    before you can program it.

        Command Register (Port 08h, D0h - Write)
        ========================================

            Bit(s)    Function
            --------  ------------------------------------------------------
            7         1 = DACK (DMA Acknowledge) sensing active high
                      0 = DACK sensing active low (default, do not change) *
             6        1 = DRQ (DMA Request) sensing active low
                      0 = DRQ sensing active high (default, do not change) *
              5       1 = Extended write mode ** \ Irrelevant
                      0 = Late write mode **     /  if b3=1
               4      1 = Rotating priority
                      0 = Fixed priority (default)
                3     1 = Compressed timing (???)    \ Irrelevant
                      0 = Uncompressed timing (???)  /  if b0=1
                 2    1 = Controller disabled
                      0 = Controller enabled
                  1   1 = Enable channel 0 (4?) address hold ***
                      0 = Disable channel 0 (4?) address hold ***
                   0  1 = Enable memory-to-memory transfer (8-bit only?)
                      0 = Disable memory-to-memory transfer (8-bit only?)

            *   b7,b6 require hardware modifications for usage.
            **  b5 used for mem-mem xfers. [CH]
            *** If set, channel 0 will not increment/decrement its address.
                This can be used to set a block of memory to one value with a
                mem-mem transfer, similar to REP STOSB.

        Status Register (Port 08h, D0h - Read)
        ======================================

            Bit(s)    Function
            --------  ------------------------------------------------------
            7         1 = Channel 3/7 has a request pending
             6        1 = Channel 2/6 has a request pending
              5       1 = Channel 1/5 has a request pending
               4      1 = Channel 0/4 has a request pending
                3     1 = Channel 3/7 at terminal count (transfer done)
                 2    1 = Channel 2/6 at terminal count (transfer done)
                  1   1 = Channel 1/5 at terminal count (transfer done)
                   0  1 = Channel 0/4 at terminal count (transfer done)

            If a request is pending, it means that some device has asserted
            that channel's DRQ line on the bus and is requesting a DMA
            transfer.  These bits don't really have any use other than code
            testing.  [CH]

        Request Register (Port 09h, D2h - Write)
        ========================================

            Bit(s)    Function
            --------  ------------------------------------------------------
            76543     Unused
                 2    1 = Set request
                      0 = Reset request
                  10  Channel number (binary)

            If the request bit is set, the channel will begin transferring
            regardless of the DREQ state.  Used to start DMA transfers via
            software, such as mem-mem transfers. [BW]

        Single Mask Register (Port 0Ah, D4h - Write)
        ============================================

            Bit(s)    Function
            --------  ------------------------------------------------------
            76543     Unused
                 2    1 = Set mask (disable channel)
                      0 = Reset mask (enable channel)
                  10  Channel number (binary)

        Mode Register (Port 0Bh, D6h - Write)
        =====================================

            Bit(s)    Function
            --------  ------------------------------------------------------
            76        Select transfer mode*:
                        00 = Demand mode
                        01 = Single mode
                        10 = Block mode
                        11 = Cascade mode
              5       1 = Select address decrement
                      0 = Select address increment
               4      1 = Enable auto-init (continuous loop)
                      0 = Disable auto-init (single xfer)
                32    Transfer type:
                        00 = Verify (NOP)
                        01 = Write (to memory)
                        10 = Read (from memory)
                        11 = Undefined
                  10  Channel number (binary)

            * Demand mode: "Normal mode for ... slow devices." [CH]
              Single mode: I use this for the SB.  Description, anyone?
              Block mode: "Everything else uses block mode." [CH]
                Supposedly used for mem-mem transfers also. (?)
              Cascade mode: Cascade mode is used ONLY for channel 4.  BIOS
                programs channel 4 at boot for cascading; don't mess with it
                or you will disable all the high DMA channels!

        Clear F/F Register (Port 0Ch, D8h - Write)
        ==========================================
            Also called "Clear Byte Pointer Register".  Any write to this
            register resets the internal pointers for the 16-bit registers.
            The 16-bit registers accept two bytes in sequence, low byte first.
            By writing to this register, you ensure that the program and the
            DMA controller are in sync.  This is useful because some errant
            program may inadvertently write a single value to a 16-bit DMA
            register, getting it "out of sync".

        Master Reset Register (Port 0Dh, DAh - Write)
        =============================================
            Any write to this register resets the controller and all four of
            its channels.

        Master Enable Register (Port 0Eh, DCh - Write)
        ==============================================
            Any write to this register enables all four channels on the
            controller.

        Master Mask Register (Port 0Fh, DEh - Write)
        ============================================
            Allows all channels to be programmed (enabled or disabled) at the
            same time.

            Bit(s)    Function
            --------  ------------------------------------------------------
            7654      Unused
                3     1 = Disable channel 3/7
                      0 = Enable channel 3/7
                 2    1 = Disable channel 2/6
                      0 = Enable channel 2/6
                  1   1 = Disable channel 1/5
                      0 = Enable channel 1/5
                   0  1 = Disable channel 0/4
                      0 = Enable channel 0/4

        Page Registers (Write only)
        ===========================
            The page registers indicate the "page" of memory for a transfer.
            A page is a block of 64k (or 128k) that is used as a base for the
            transfer, much as a segment register is used as a base for memory
            addressing.  The 8-bit channels only use the lower 4 bits of the
            page value, so they are limited to the first meg of memory.  The
            16-bit channels use all 8 bits of the page value.  In addition,
            the 16-bit pages are 128k long since the transfers are in words.
            So the 16-bit channels may access the first 32 megs of memory.  In
            286 systems, there are only 24 address lines, so bit 7 should
            always be 0.  In 386+ systems, bit 7 may or may not be used,
            depending on the motherboard design.

            Page Register (8-bit channel)
            -----------------------------
            xxxxPPPP : Page number, 0 to 15

            Page Register (16-bit channel)
            ------------------------------
            PPPPPPPP : Page number, 0 to 255

        Offset Registers (Write/Read)
        =============================
            Writing to the offset registers sets the starting offset for a
            transfer.  Reading from the offset registers gives the current
            offset in an active DMA transfer.  These registers are 16 bits
            wide for both 8-bit and 16-bit channels.  For 8-bit channels, the
            offset is indicated directly.  For 16-bit channels, the offset is
            indicated in words.  This means that the value programmed into the
            offset register must be half the actual offset, and the transfers
            must be word aligned.  The offset registers use standard Intel
            word ordering; send/read the low byte of the offset first,
            followed by the high byte.  It's a good idea to reset the byte
            pointer flip/flop before reading or writing to this register to
            ensure that your program and the controller are in sync.  The
            physical address for a DMA transfer is:

                 8-bit channels: Page * 128k + Offset
                16-bit channels: Page * 128k + (Offset * 2)

        Block Size / Countdown Registers (Write/Read)
        =============================================
            Writing to the block size registers sets the DMA block length.
            Reading from the countdown registers gives the remaining block
            size MINUS ONE for an active DMA transfer.  When a transfer is
            complete, the countdown register is equal to -1 (0FFFFh).  Note
            that both registers reside at the same port address; the one
            selected depends on whether you are reading or writing.  These
            registers indicate the transfer length in bytes (8-bit) or words
            (16-bit). This value is the actual block length MINUS ONE.  This
            allows a full 64k (or 128k) block to be transferred at a time.
            These are 16-bit registers, so the maximum transfer size for the
            8-bit channels is 64k, and the maximum transfer size for the
            16-bit channels is 128k.

    The order in which you program the registers doesn't really matter, as
    long as you disable the channel first and reset the Byte F/F before
    programming the 16-bit registers.  I have found that a convenient method
    for DMA programming goes something like this:

        1.  Disable channel
        2.  Reset Byte F/F
        3.  Set mode
        4.  Set page
        5.  Set offset
        6a. Set block length
        6b. Send block length to peripheral
        7.  Enable channel
        8.  Program peripheral to receive DMA

SAMPLE CODE
===========
    The following code segments demonstrate how to use DMA.  I can't figure
    out the memory-memory transfers, but I've included code that should work
    if given the proper Command Register byte.  This code will run under TASM
    in Ideal mode as listed. If you are using MASM (or MASM mode), reverse the
    PROC and ENDP statements as indicated.  If you are using any other
    assembler that doesn't support MASM syntax, you're on your own.  These
    routines use multiple bit shifts, so you must enable 286+ processing (with
    P286N or .286).

    ;***********************************************************************
    ;* DMA8 - Setup an 8-bit DMA transfer.                                 *
    ;* Entry:    AH = DMA channel (0-3)                                    *
    ;*           AL = Mode (see below)                                     *
    ;*        ES:DI = 8-bit DMA normalized transfer address                *
    ;*           CX = Block length (64k=0000h)                             *
    ;* Exit :    CX = Block length minus one                               *
    ;*           CF = 1 : Page overflow, transfer not attempted            *
    ;*           CF = 0 : Transfer in progress                             *
    ;* Regs : AX, BX, DX modified                                          *
    ;* Note : Common modes for DMA programming are:                        *
    ;*        44h : Single Write - Write block to memory (from buss)       *
    ;*        48h : Single Read - Read block from memory (to buss)         *
    ;*        54h : Auto-Init Write - Write block to memory with looping   *
    ;*        58h : Auto-Init Read - Read block from memory with looping   *
    ;***********************************************************************
    PROC        DMA8                            ;"DMA8 PROC" for MASM
                and     ah,3                    ;Ensure channel correct
                and     al,NOT 3                ;Ensure mode correct
                or      ah,al                   ;Combine them
                dec     cx                      ;Fixup length

                mov     dx,di                   ;Check for overflow
                add     dx,cx
                jo      @@BadAddr

                mov     al,ah                   ;Disable channel
                and     al,3
                or      al,100b
                out     0Ah,al

                xor     al,al                   ;Reset byte F/F
                out     0Ch,al

                mov     al,ah                   ;Set mode
                out     0Bh,al

                mov     dx,2137h                ;Magic DMA page reg convert
                mov     cl,ah                   ;  for DMA0..3
                shl     cl,2                    ;  DMA0 => 87h
                shr     dx,cl                   ;  DMA1 => 83h
                and     dx,0000Fh               ;  DMA2 => 81h
                add     dx,00080h               ;  DMA3 => 82h

                mov     bx,es                   ;Set page
                mov     al,bl
                out     dx,al

                mov     dl,ah                   ;Set offset
                and     dl,3
                shl     dl,1                    ;Port = Channel*2
                mov     bx,di
                mov     al,bl
                out     dx,al
                mov     al,bh
                out     dx,al

                inc     dl                      ;Set block length
                mov     al,cl                   ;Port = Channel*2 + 1
                out     dx,al
                mov     al,ch
                out     dx,al

                mov     al,ah                   ;Enable channel
                and     al,3
                out     0Ah,al

    ;*** Setup peripheral here

                clc                             ;Indicate success
                ret

    @@BadAddr:  stc                             ;Indicate failure
                ret
    ENDP                                        ;"DMA8 ENDP" for MASM

    ;***********************************************************************
    ;* DMA16 - Setup a 16-bit DMA transfer.                                *
    ;* Entry:    AH = DMA channel (4-7)                                    *
    ;*           AL = Mode (see below)                                     *
    ;*        ES:DI = 16-bit DMA normalized transfer address               *
    ;*           CX = Block length in words (128k=0000h)                   *
    ;* Exit :    CX = Block length in words minus one                      *
    ;*           CF = 1 : Page overflow, transfer not attempted            *
    ;*           CF = 0 : Transfer in progress                             *
    ;* Regs : AX, BX, DX modified                                          *
    ;* Note : Common modes for DMA programming are:                        *
    ;*        44h : Single Write - Write block to memory (from buss)       *
    ;*        48h : Single Read - Read block from memory (to buss)         *
    ;*        54h : Auto-Init Write - Write block to memory with looping   *
    ;*        58h : Auto-Init Read - Read block from memory with looping   *
    ;***********************************************************************
    PROC        DMA16                           ;"DMA16 PROC" for MASM
                and     ah,3                    ;Ensure channel correct
                and     al,NOT 3                ;Ensure mode correct
                or      ah,al                   ;Combine them
                dec     cx                      ;Fixup length

                mov     dx,di                   ;Check for overflow
                add     dx,cx
                jo      @@BadAddr

                mov     al,ah                   ;Disable channel
                and     al,3
                or      al,100b
                out     0D4h,al

                xor     al,al                   ;Reset byte F/F
                out     0D8h,al

                mov     al,ah                   ;Set mode
                out     0D6h,al

                mov     dx,0A9BFh               ;Magic DMA page reg convert
                mov     cl,ah                   ;  for DMA4..7
                shl     cl,2                    ;  DMA4 => 8Fh
                shr     dx,cl                   ;  DMA5 => 8Bh
                and     dx,0000Fh               ;  DMA6 => 89h
                add     dx,00080h               ;  DMA7 => 8Ah

                mov     bx,es                   ;Set page
                mov     al,bl
                out     dx,al

                mov     dl,ah                   ;Set offset
                and     dl,3
                shl     dl,2                    ;Port = Channel*4 + C0h
                add     dl,0C0h
                mov     bx,di
                mov     al,bl
                out     dx,al
                mov     al,bh
                out     dx,al

                inc     dl                      ;Set block length
                mov     al,cl                   ;Port = Channel*2 + C0h + 1
                out     dx,al
                mov     al,ch
                out     dx,al

                mov     al,ah                   ;Enable channel
                and     al,3
                out     0D4h,al

    ;*** Setup peripheral here

                clc                             ;Indicate success
                ret

    @@BadAddr:  stc                             ;Indicate failure
                ret
    ENDP                                        ;"DMA16 ENDP" for MASM

    ;***********************************************************************
    ;* DMA8_M2M - Setup an 8-bit memory-to-memory transfer (INCOMPLETE)    *
    ;* Entry: DS:SI = 8-bit DMA normalized source address                  *
    ;*        ES:DI = 8-bit DMA normalized destination address             *
    ;*           CX = Block length in bytes (64k=0000h)                    *
    ;* Exit :    CX = Block length minus one                               *
    ;*           CF = 1 : Page overflow, transfer not attempted            *
    ;*           CF = 0 : Transfer in progress                             *
    ;* Regs : AX, DX modified                                              *
    ;* Note : Uses DMA channels 0 and 1.  WHY WON'T THIS WORK?             *
    ;***********************************************************************
    PROC        DMA8_M2M                        ;"DMA8_M2M PROC" for MASM
                stc                             ;Not functional!
                ret                             ;Exit
                dec     cx                      ;Fixup length

                mov     ax,si                   ;Check for overflow
                add     ax,cx
                jo      @@BadAddr
                mov     ax,di
                add     ax,cx
                jo      @@BadAddr

                mov     al,100b                 ;Disable DMA0
                out     0Ah,al
                mov     al,101b                 ;Disable DMA1
                out     0Ah,al

                mov     al,00000001b            ;Set command reg
                out     08h,al

                xor     al,al                   ;Reset byte F/F
                out     0Ch,al

                mov     al,10001000b            ;DMA0: Block/Inc/Read
                out     0Bh,al
                mov     al,10000101b            ;DMA1: Block/Inc/Write
                out     0Bh,al

                mov     dx,ds                   ;DMA0: Page = DS
                mov     al,dl
                out     87h,al
                mov     dx,es                   ;DMA1: Page = ES
                mov     al,dl
                out     83h,al

                mov     dx,si                   ;DMA0: Offset SI
                mov     al,dl
                out     00h,al
                mov     al,dh
                out     00h,al
                mov     dx,di                   ;DMA1: Offset DI
                mov     al,dl
                out     02h,al
                mov     al,dh
                out     02h,al

                mov     al,cl                   ;DMA0: Length CX
                out     01h,al
                mov     al,ch
                out     01h,al
                mov     al,cl                   ;DMA1: Length CX
                out     03h,al
                mov     al,ch
                out     03h,al

                mov     al,000b                 ;Enable DMA0
                out     0Ah,al
                mov     al,001b                 ;Enable DMA1
                out     0Ah,al

                mov     al,100b                 ;Set DREQ0
                out     09h,al

                clc                             ;Indicate success
                ret

    @@BadAddr:  stc                             ;Indicate failure
                ret
    ENDP                                        ;"DMA8_M2M ENDP" for MASM

    ;***********************************************************************
    ;* NormDMA8 - Normalize a seg:ofs pointer to 8-bit DMA page:ofs form.  *
    ;* Entry: ES:DI = Pointer                                              *
    ;* Exit : ES:DI = DMA normalized pointer                               *
    ;* Regs : AL, DX modified                                              *
    ;***********************************************************************
    PROC        NormDMA8                        ;"NormDMA8 PROC" for MASM
                push    ax
                push    dx

                mov     dx,es                   ;Load DX and AX with segment
                mov     ax,dx                   ;  for manipulation
                shr     dx,12                   ;Page to DX
                shl     ax,4                    ;Segment fixup to AX
                add     di,ax                   ;Add to offset
                adc     dx,0                    ;Overflow bumps page
                mov     es,dx                   ;Save page in DS

                pop     dx
                pop     ax
                ret
    ENDP                                        ;"NormDMA8 ENDP" for MASM

    ;***********************************************************************
    ;* DMA8Stat - Check status of 8-bit DMA transfer.                      *
    ;* Entry: AH = DMA channel                                             *
    ;* Exit : CX = Bytes remaining MINUS ONE or -1 if done                 *
    ;* Regs : AL modified                                                  *
    ;* Note : To check 16-bit DMA, change the port to Channel*4 + 1 + C0h. *
    ;*        CX will be returned as WORDS remaining.                      *
    ;***********************************************************************
    PROC        DMA8Stat                        ;"DMA8Stat PROC" for MASM
                push    dx

                xor     al,al                   ;Reset byte F/F
                out     0Ch,al

                and     ah,3                    ;Ensure channel correct
                xor     dx,dx                   ;Port = Channel*2 + 1
                mov     dl,ah
                shl     dl,1
                inc     dl

                in      al,dx                   ;Get low byte
                mov     cl,al
                in      al,dx                   ;Get high byte
                mov     ch,al

                pop     dx
                ret
    ENDP                                        ;"DMA8Stat ENDP" for MASM

    ;***********************************************************************
    ;* DMA8BlkReq - Request 64k block of memory for 8-bit DMA transfers.   *
    ;* Entry: None                                                         *
    ;* Exit : NC = Success                                                 *
    ;*          ES = MCB Handle                                            *
    ;*          DX = Base DMA Segment                                      *
    ;*         C = Failure                                                 *
    ;*          AX = Error code from DOS                                   *
    ;* Regs : AX, BX                                                       *
    ;* Note : Must have 128k free due to limits of segment architecture.   *
    ;*        MCB length adjusted before exit (between 64k-128k).          *
    ;***********************************************************************
    PROC        DMA8BlkReq                      ;"DMA8BlkReq PROC" for MASM
                mov     bx,1FFFh                ;Request 128k-16
                mov     ah,48h
                int     21h
                jc      @@Error

                mov     dx,ax                   ;DX=AX=Handle
                add     dx,00FFFh               ;Calc Base
                and     dx,0F000h               ;  =(Handle+0FFFh) && F000h
                mov     bx,dx                   ;Calc new length
                sub     bx,ax                   ;  =(Base-Handle)+1000h
                add     bx,1000h

                mov     es,ax
                mov     ah,4Ah                  ;Adjust block size
                int     21h
    @@Error:    ret
    ENDP                                        ;"DMA8BlkReq ENDP" for MASM

***EOF***

