How to : Program the DMA controllers (8/16 bit)
Written by : Peter Quiring (Feb 1/97)
The DMA controller is used to read/write from devices without having to use
the CPU. This makes it possible to send a WAV file to your Sound Blaster
and still play your game at the same time.
This tutorial will show you have to setup the DMA controller to send or
read data to/from a device, including how to "properly" program the new
16bit DMA controllers.
Before you program the DMA controller there are few things you need to know.
- The beginning address of your DMA buffer
- The size of your buffer
- Which DMA port to use (there are 7 total, 0-3 and 5-7)
DMA channels 0-3 are 8bit (which means they can send/receive only 8bits
at a time) and channels 5-7 are 16bit only. Note channel 4 is used to cascade
the DMA controllers so that the new 16bit channels could be added.
Now if you don't know anything about DMA controller (the 8237) it is very
old and ugly. The buffer you alloc from DOS to either be given to the device
or filled by data coming from the device must NOT go over a 64k page boundry.
The address you give to the DMA controller is split up into 2 parts. The
page and the offset. The page is bits 23-16 of the physical address, and
the offset is bits 15-0. As the DMA controller moves thru your buffer it
increments the offset you have given to it but it never increments the page.
So as you can see your DMA buffer must be completely inside one of these
64k pages and can not be bigger than 64k. Also note that the buffer
must be below 16MBs, so to avoid using RAM above that don't alloc it from XMS,
use DOS memory only.
Here is some code that will alloc a buffer that will never cross a boundry:
typedef unsigned long int dword;
dword getbuffer(dword size) { //size can not exceed 64k
dword addr,page1,page2;
addr=(dword)malloc(size*2);
if (addr==0) return 0; //0=NULL
page1=addr & 0xff0000h;
page2=(addr + size - 1) & 0xff0000h;
if (page1 != page2) {
//the 1st part of the buffer crosses a 64k thing so we shall
// start our buffer
return (page2);
} else {
return (addr);
}
}
This will always return successful (unless of course malloc() fails) but
it requires to alloc twice as much as needed. If page1 != page2 then if our
buffer were to start at addr it would cross a boundry. In this case
we simple return page2 which will still be inside of the alloc block of RAM
and will be big enough for the buffer size requested and will not cross
a boundry.
Now that you have allocated RAM you need to LOCK the RAM to prevent virtual
enviroments (such as Windoze) to swap out your buffer when it is being used
by the DMAC. Use DPMI func #600.
When using the 16bit DMA channels (5-7) your buffer can be 128k and can not
cross a 128k boundry.
All of this is done in QLIB v2.00:
- alloc_dma64() - alloc a 64k buffer (for 8bit DMAs)
- alloc_dma128() - alloc a 128k buffer (for 16bit DMAs)
- free_dma() - free 64/128k buffer
Note: the alloc...() functions also alloc RAM a little cleaner then shown above.
Ok, now you are ready to program the DMA controller. First I'll show how
to program the 8bit DMA controllers.
The DMA controller has ports specific to each DMA channel, they are
the page register, offset register and length registers:
DMA Channel PAGE ADDRESS LENGTH
0 87h 0 1
1 83h 2 3
2 81h 4 5
3 82h 6 7
The rest of the ports do not depend on which channel you plan on using:
MASK 0ah
MODE 0bh
CLEAR 0ch
The MASK port is as follows:
bit 7 - 3 = 0 Reserved
bit 2 = 0 clear mask
= 1 set mask
bits 1 - 0 = 00 Select channel 0
= 01 select channel 1
= 10 select channel 2
= 11 select channel 3
This port is used to tell the DMAC which DMA channel you are going to program.
You set the mask for the channel, program the DMAC, clear the mask for
the channel.
The MODE port is as follows:
bit 7 - 6 = 00 Demand mode
= 01 Signal mode
= 10 Block mode
= 11 Cascade mode
bit 5 = 0 Reserved
bit 4 = 0 Normal mode
= 1 Autoinitilize mode
bit 3 - 2 = 00 Verify operation
= 01 Write operation
= 10 Read operation
= 11 Reserved
bits 1 - 0 = 00 Select channel 0
= 01 select channel 1
= 10 select channel 2
= 11 select channel 3
When using sound cards (48h+DMA channel) would be for playback of a WAV, and
(44h+DMA channel) would be for recording a WAV.
If bit 4 is 0, then after the DMA buffer is send/received from the device
the DMAC will quit. But if bit 4 is 1 (AutoInit mode) the DMAC will reset
the offset and length to the programmed values and continue to operate. This
is a very clever thing that can be used in things such as double-buffering.
I will explain this later.
The CLEAR port is used just to clear all register in the DMAC and must you
must simply write zero to it each time you program the DMAC (right after
you mask the channel).
When setting the length of the buffer you must give 1 less to the DMAC.
Therefore 0=1 sample, 1=2 samples, etc.
OK, here is an example:
byte pageport[]={0x87,0x83,0x81,0x82};
// defines for direction
#define PLAYBACK 0
#define RECORD 1
void setupDMA8(dword addr,dword size,byte dma,byte dir) {
byte page;
word offset;
byte mode;
size--; //size must be 1 less than requested
page = (addr >> 16) & 0xff;
offset = addr & 0xffff;
outp(0xa,4+dma); //mask DMA channel
outp(0xc,0); //clear DMA
outp(pageport[dma],page); //set page
outp(dma*2,offset & 0xff); //set offset (LO BYTE)
outp(dma*2,(offset & 0xff00) >> 8); //set offset (HI BYTE)
outp(dma*2+1,size & 0xff); //set size (LO BYTE)
outp(dma*2+1,(size & 0xff00) >> 8); //set size (HI BYTE)
if (dir==PLAYBACK) mode=44h; else mode==48h;
mode+=dma;
// mode+=0x10; //add this to use Auto DMA mode
outp(0xb,mode); //set mode
outp(0xa,dma); //unmask DMA
}
That's it!
Now for 16bit DMA setup which is almost the same except for all new ports
and a messed up addressing format.
Here's the ports:
DMA Channel PAGE ADDRESS LENGTH
4 8Fh C0h C2h
5 8Bh C4h C6h
6 89h C8h CAh
7 8Ah CCh CEh
MASK = 0xd4
bit 7 - 3 = 0 Reserved
bit 2 = 0 clear mask
= 1 set mask
bits 1 - 0 = 00 Select channel 4
= 01 select channel 5
= 10 select channel 6
= 11 select channel 7
MODE = 0xd6
bit 7 - 6 = 00 Demand mode
= 01 Signal mode
= 10 Block mode
= 11 Cascade mode
bit 5 = 0 Reserved
bit 4 = 0 Normal mode
= 1 Auto Init mode
bit 3 - 2 = 00 Verify operation
= 01 Write operation
= 10 Read operation
= 11 Reserved
bits 1 - 0 = 00 Select channel 4
= 01 select channel 5
= 10 select channel 6
= 11 select channel 7
CLEAR = 0xd8
These all are used the same way as in the 8bit version except the addressing
is different because 16bit words are used.
Here is an example, NOTE how PAGE and OFFSET are setup:
byte pageport16[]={0x8f,0x8b,0x89,0x8a};
// defines for direction (dir)
#define PLAYBACK 0
#define RECORD 1
void setupDMA16(dword addr,dword size,byte dma,byte dir) {
byte page;
word offset;
byte mode;
size--; //size must be 1 less than requested
page = (addr >> 16) & 0xff;
offset = (addr & 0xffff) >> 1;
offset += (page & 0x1) << 15; outp(0xd4,dma); //mask DMA channel outp(0xd8,0); //clear DMA outp(pageport16[dma-4],page); //set page outp(0xc0+(dma-4)*4,offset & 0xff); //set offset (LO BYTE) outp(0xc0+(dma-4)*4,(offset & 0xff00)>> 8); //set offset (HI BYTE)
outp(0xc0+(dma-4)*4+2,size & 0xff); //set size (LO BYTE)
outp(0xc0+(dma-4)*4+2,(size & 0xff00) >> 8); //set size (HI BYTE)
if (dir==PLAYBACK) mode=44h; else mode==48h;
mode+=dma;
// mode+=0x10; //add this to use Auto DMA mode
outp(0xd6,mode); //set mode
outp(0xd4,dma-4); //unmask DMA
}
Notes:
The size represents the number of 16bit words to transfer.
The address must be word aligned or garbage will be transfered.
Auto Init DMA
Auto Init DMA can be used to setup double buffering technique. Basically
what you do is setup a DMA buffer to size 'X'. And then you program your
device to size 'X/2'. There fore your DMA buffer is split up into two equal
parts.
In the case of playback you will fill both parts of the buffer right away.
Then start the device on the 1st block, when the IRQ is triggered you
allow the device to imediatly continue on the next block and the DMAC will
not need any programming to continue. During this IRQ you set a flag
so the mainstream of the program knows that buffer #1 is used and can be
loaded with the 3rd portion of the data. When the 2nd IRQ comes you know
the device has finished the 2nd part of the buffer and is ready for more,
if the mainstream had enough time to fill buffer #1 you can allow the device
to use that, and the DMAC does not have to be reprogramed because in
Auto init mode it will wrap around (reset itself) to the beginning of the
buffer. And this is repeated until your data is exhausted.
The SB16 also supports Auto-init mode so you don't have to reprogram it
for each block transfer. You can still use Autoinit DMA with older Sound
Blaster, but you will have to reprogram the sound blaster for each
transfer.
But using the Sound Blaster will come in another tutorial
some day.
This concludes this tutorial. Have a nice day...(or night :>)
Sources:
DMA_VLA.TXT - a good file but missing info on things like messed up 16bit
addressing mode and such things.
Autoinit.EXE - from Creative Labs, I found in sources how that damned 16bit
addressing mode works.
Copyright © 1995-2005 Nexus Systems
Privacy