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