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.
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:
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:

BACK
Copyright © 1995-2005 Nexus Systems Privacy
SourceForge.net Logo