383 lines
14 KiB
Plaintext
383 lines
14 KiB
Plaintext
+---------------------[ 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) ((x & 0xFF00) >> 8)
|
|
|
|
/* Quick-access registers and ports for each DMA channel. */
|
|
uchar MaskReg[8] = { 0x0A, 0x0A, 0x0A, 0x0A, 0xD4, 0xD4, 0xD4, 0xD4 };
|
|
uchar ModeReg[8] = { 0x0B, 0x0B, 0x0B, 0x0B, 0xD6, 0xD6, 0xD6, 0xD6 };
|
|
uchar ClearReg[8] = { 0x0C, 0x0C, 0x0C, 0x0C, 0xD8, 0xD8, 0xD8, 0xD8 };
|
|
|
|
uchar PagePort[8] = { 0x87, 0x83, 0x81, 0x82, 0x8F, 0x8B, 0x89, 0x8A };
|
|
uchar AddrPort[8] = { 0x00, 0x02, 0x04, 0x06, 0xC0, 0xC4, 0xC8, 0xCC };
|
|
uchar CountPort[8] = { 0x01, 0x03, 0x05, 0x07, 0xC2, 0xC6, 0xCA, 0xCE };
|
|
|
|
void StartDMA(uchar DMA_channel, DMA_block *blk, uchar mode)
|
|
{
|
|
/* First, make sure our 'mode' is using the DMA channel specified. */
|
|
mode |= DMA_channel;
|
|
|
|
/* Don't let anyone else mess up what we're doing. */
|
|
disable();
|
|
|
|
/* Set up the DMA channel so we can use it. This tells the DMA */
|
|
/* that we're going to be using this channel. (It's masked) */
|
|
outportb(MaskReg[DMA_channel], 0x04 | DMA_channel);
|
|
|
|
/* Clear any data transfers that are currently executing. */
|
|
outportb(ClearReg[DMA_channel], 0x00);
|
|
|
|
/* Send the specified mode to the DMA. */
|
|
outportb(ModeReg[DMA_channel], mode);
|
|
|
|
/* Send the offset address. The first byte is the low base offset, the */
|
|
/* second byte is the high offset. */
|
|
outportb(AddrPort[DMA_channel], LOW_BYTE(blk->offset));
|
|
outportb(AddrPort[DMA_channel], HI_BYTE(blk->offset));
|
|
|
|
/* Send the physical page that the data lies on. */
|
|
outportb(PagePort[DMA_channel], blk->page);
|
|
|
|
/* Send the length of the data. Again, low byte first. */
|
|
outportb(CountPort[DMA_channel], LOW_BYTE(blk->length));
|
|
outportb(CountPort[DMA_channel], HI_BYTE(blk->length));
|
|
|
|
/* Ok, we're done. Enable the DMA channel (clear the mask). */
|
|
outportb(MaskReg[DMA_channel], DMA_channel);
|
|
|
|
/* Re-enable interrupts before we leave. */
|
|
enable();
|
|
}
|
|
|
|
void PauseDMA(uchar DMA_channel)
|
|
{
|
|
/* All we have to do is mask the DMA channel's bit on. */
|
|
outportb(MaskReg[DMA_channel], 0x04 | DMA_channel);
|
|
}
|
|
|
|
void UnpauseDMA(uchar DMA_channel)
|
|
{
|
|
/* Simply clear the mask, and the DMA continues where it left off. */
|
|
outportb(MaskReg[DMA_channel], DMA_channel);
|
|
}
|
|
|
|
void StopDMA(uchar DMA_channel)
|
|
{
|
|
/* We need to set the mask bit for this channel, and then clear the */
|
|
/* selected channel. Then we can clear the mask. */
|
|
outportb(MaskReg[DMA_channel], 0x04 | DMA_channel);
|
|
|
|
/* Send the clear command. */
|
|
outportb(ClearReg[DMA_channel], 0x00);
|
|
|
|
/* And clear the mask. */
|
|
outportb(MaskReg[DMA_channel], DMA_channel);
|
|
}
|
|
|
|
uint DMAComplete(uchar DMA_channel)
|
|
{
|
|
/* Register variables are compiled to use registers in C, not memory. */
|
|
register int z;
|
|
|
|
z = CountPort[DMA_channel];
|
|
outportb(0x0C, 0xFF);
|
|
|
|
/* This *MUST* be coded in Assembly! I've tried my hardest to get it */
|
|
/* into C, and I've had no success. :( (Well, at least under Borland.) */
|
|
redo:
|
|
asm {
|
|
mov dx,z
|
|
in al,dx
|
|
mov bl,al
|
|
in al,dx
|
|
mov bh,al
|
|
|
|
in al,dx
|
|
mov ah,al
|
|
in al,dx
|
|
xchg ah,al
|
|
|
|
sub bx,ax
|
|
cmp bx,40h
|
|
jg redo
|
|
cmp bx,0FFC0h
|
|
jl redo
|
|
}
|
|
return _AX;
|
|
}
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
I think all the above functions are self explanatory except for the last
|
|
one. The last function returns the number of bytes that the DMA has
|
|
transferred to (or read from) the device. I really don't know how it works
|
|
as it's not my code. I found it laying on my drive, and I thought it might
|
|
be somewhat useful to those of you out there. You can find out when a DMA
|
|
transfer is complete this way if the I/O card doesn't raise an interrupt.
|
|
DMAComplete() will return -1 (or 0xFFFF) if there is no DMA in progress.
|
|
|
|
Don't forget to load the length into your DMA_block structure as well
|
|
before you call StartDMA(). (When I was writing these routines, I forgot
|
|
to do that myself... I was wondering why it was transferring garbage.. <G>)
|
|
|
|
I hope you all have caught on to how the DMA works by now. Basically it
|
|
keeps a list of DMA channels that are running or not. If you need to change
|
|
something in one of these channels, you mask the channel, and reprogram. When
|
|
you're done, you simply clear the mask, and the DMA starts up again.
|
|
|
|
If anyone has problems getting this to work, I'll be happy to help. Send
|
|
us mail at the address below, and either I or another Rage member will fix
|
|
your problem(s).
|
|
|
|
Enjoy!
|
|
- Night Stalker
|