+———————[ Rage Technologies, Inc. ]————————-+
How to program the DMA - by Night Stalker
—————————————————————————–
When you want to start a DMA transfer, you need to know three things:
Where the memory is located (what page),
The offset into the page, and
How much you want to transfer.
Since the DMA can work in both directions (memory to I/O card, and I/O
card to memory), you can see how the Sound Blaster can record as well as
play by using DMA.
The DMA has two restrictions which you must abide by:
You cannot transfer more than 64K of data in one shot, and
You cannot cross a page boundary.
Restriction #1 is rather easy to get around. Simply transfer the first
block, and when the transfer is done, send the next block.
For those of you not familiar with pages, I'll try to explain.
Picture the first 1MB region of memory in your system. It is divided
into 16 pages of 64K a piece like so:
Page Segment:Offset address
---- ----------------------
0 0000:0000 - 0000:FFFF
1 1000:0000 - 1000:FFFF
2 2000:0000 - 2000:FFFF
. .000:0000 - .000:FFFF
Okay, remember the three things needed by the DMA? Look back if you
need to. We can stuff this data into a structure for easy accessing:
typedef struct
{
char page;
unsigned int offset;
unsigned int length;
} DMA_block;
Now, how do we find a memory pointer's page and offset? Easy. Use
the following code:
void LoadPageAndOffset(DMA_block *blk, char *data)
{
unsigned int temp, segment, offset;
unsigned long foo;
segment = FP_SEG(data);
offset = FP_OFF(data);
blk->page = (segment & 0xF000) >> 12;
temp = (segment & 0x0FFF) << 4;
foo = offset + temp;
if (foo > 0xFFFF)
blk->page++;
blk->offset = (unsigned int)foo;
}
Most (if not all) of you are probably thinking, "What the heck is he doing
there?" I'll explain.
The FP_SEG and FP_OFF macros find the segment and the offset of the data
block in memory. Since we only need the page (look back at the table above),
we can take the upper 4 bits of the segment to create our page.
The rest of the code takes the segment, adds the offset, and sees if the
page needs to be advanced or not. (Note that a memory region can be located at
2FFF:F000, and a single byte increase will cause the page to increase by one.)
In plain English, the page is the highest 4 bits of the absolute 20 bit
address of our memory location. The offset is the lower 12 bits of the
absolute 20 bit address plus our offset.
Now that we know where our data is, we need to find the length.
The DMA has a little quirk on length. The true length sent to the DMA
is actually length + 1. So if you send a zero length to the DMA, it actually
transfers one byte, whereas if you send 0xFFFF, it transfers 64K. I guess
they made it this way because it would be pretty senseless to program the
DMA to do nothing (a length of zero), and in doing it this way, it allowed a
full 64K span of data to be transferred.
Now that you know what to send to the DMA, how do you actually start it?
This enters us into the different DMA channels.
The DMA has 4 different channels to send 8-bit data. These channels are
0, 1, 2, and 3, respectively. You can use any channel you want, but if you're
transferring to an I/O card, you need to use the same channel as the card.
(ie: Sound Blaster uses DMA channel 1 as a default.)
There are 3 ports that are used to set the DMA channel:
The page register,
The address (or offset) register, and
The word count (or length) register.
The following chart will describe each channel and it's corresponding
port number:
DMA Channel Page Address Count
------------------------------------
0 87h 0h 1h
1 83h 2h 3h
2 81h 4h 5h
3 82h 6h 7h
4 8Fh C0h C2h
5 8Bh C4h C6h
6 89h C8h CAh
7 8Ah CCh CEh
(Note: Channels 4-7 are 16-bit DMA channels. See below for more info.)
Since you need to send a two-byte value to the DMA (the offset and the
length are both two bytes), the DMA requests you send the low byte of data
first, then the high byte. I'll give a thorough example of how this is done
momentarily.
The DMA has 3 registers for controlling it's state. Here is the bitmap
layout of how they are accessed:
Mask Register (0Ah):
MSB LSB
x x x x x x x x
------------------- - -----
| | | 00 - Select channel 0 mask bit
| | +---- 01 - Select channel 1 mask bit
| | 10 - Select channel 2 mask bit
| | 11 - Select channel 3 mask bit
| |
| +---------- 0 - Clear mask bit
| 1 - Set mask bit
|
+----------------------- xx - Don't care
Mode Register (0Bh):
MSB LSB
x x x x x x x x
----- - - ----- -----
| | | | | 00 - Channel 0 select
| | | | +---- 01 - Channel 1 select
| | | | 10 - Channel 2 select
| | | | 11 - Channel 3 select
| | | |
| | | | 00 - Verify transfer
| | | +------------ 01 - Write transfer
| | | 10 - Read transfer
| | |
| | +-------------------- 0 - Autoinitialized
| | 1 - Non-autoinitialized
| |
| +------------------------ 0 - Address increment select
|
| 00 - Demand mode
+------------------------------ 01 - Single mode
10 - Block mode
11 - Cascade mode
DMA clear selected channel (0Ch):
Outputting a zero to this port stops all DMA processes that are currently
happening as selected by the mask register (0Ah).
Some of the most common modes to program the mode register are:
45h: Write transfer (I/O card to memory), and
49h: Read transfer (memory to I/O card).
Both of these assume DMA channel 1 for all transfers.
Now, there's also the 16-bit DMA channels as well. These shove two bytes
of data at a time. That's how the Sound Blaster 16 works as well in 16-bit
mode.
Programming the DMA for 16-bits is just as easy as 8 bit transfers. The
only difference is you send data to different I/O ports. The 16-bit DMA also
uses 3 other control registers as well:
Mask Register (D4h):
MSB LSB
x x x x x x x x
------------------- - -----
| | | 00 - Select channel 4 mask bit
| | +---- 01 - Select channel 5 mask bit
| | 10 - Select channel 6 mask bit
| | 11 - Select channel 7 mask bit
| |
| +---------- 0 - Clear mask bit
| 1 - Set mask bit
|
+----------------------- xx - Don't care
Mode Register (D6h):
MSB LSB
x x x x x x x x
----- - - ----- -----
| | | | | 00 - Channel 4 select
| | | | +---- 01 - Channel 5 select
| | | | 10 - Channel 6 select
| | | | 11 - Channel 7 select
| | | |
| | | | 00 - Verify transfer
| | | +------------ 01 - Write transfer
| | | 10 - Read transfer
| | |
| | +-------------------- 0 - Autoinitialized
| | 1 - Non-autoinitialized
| |
| +------------------------ 0 - Address increment select
|
| 00 - Demand mode
+------------------------------ 01 - Single mode
10 - Block mode
11 - Cascade mode
DMA clear selected channel (D8h):
Outputting a zero to this port stops all DMA processes that are currently
happening as selected by the mask register (D4h).
Now that you know all of this, how do you actually use it? Here is sample
code to program the DMA using our DMA_block structure we defined before.
/* Just helps in making things look cleaner. :) */
typedef unsigned char uchar;
typedef unsigned int uint;
/* Defines for accessing the upper and lower byte of an integer. */
#define LOW_BYTE(x) (x & 0x00FF)
#define HI_BYTE(x) 1)