-------------------------
                          |     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***