• Please review our updated Terms and Rules here

DMA addressing

pan069

Member
Joined
Jun 4, 2019
Messages
49
Hi all!

I'm playing around with some digital sound blaster stuff and I have question about DMA addressing. Various texts I have been reading through point out that a DMA transfer can't cross 64k boundries. This makes sense since that is a general limitation of real mode DOS.

However, what I am confused about is wether this applies to a phyiscal 64k page boundry or a logical one. I.e. there are 16 physical page segments, 0x0000 to 0xF000. If I wanted to have a 64k buffer, would that buffer have to start on exactly one of those physical page boundries, or, can I simply allocate a block of memory (e.g. C malloc), find the segment and offset of that pointer. Then from the segment find which phyiscal page the segment exists in and determine the physical offset of my memory block within that page? If that is true, if the physical offset turns out to be near the end of a physical page (e.g page 0x7 with offset 0xEFFF), can the DMA transfer go over that boundry? E.g. crossing from physical page 0x7000 into 0x8000?

Thanks!
 
So far as I remember it refers to physical boundaries. The DMA chip used by the PC only has 16 address lines for 64K, the top four address lines are directly connected to a 74LS670 addressable latch acting as the page register. The contents of this register directly control those address lines; there's no "segment arithmetic", so, yeah, if you want to DMA to a buffer that doesn't start on a 64K boundary you'll need to account for the fact that if you go beyond the next 64K boundary it's going to wrap around to the bottom of the same 64K the page register is set to.

(Or to put it another way, the maximum transfer size for any single DMA transfer is going to be 64K minus the starting address of your transfer unless you're okay with it wrapping around.)
 
That's only true for 8 bit DMA. 16-bit DMA can't cross 128K physical boundaries. Basically, both in XT and AT, the upper bits of an address are held in an addressable latch, which doesn't change during a DMA transfer. 16 bit DMA shifts the 8237 address one bit to the left, so you get what is, in effect a 17 bit byte address. Of course, you have to start the transfer on an even (word) boundary.

The 80186's DMA controller had no such limitation, as the address counter was a full 20 bits in length.

Some microcontrollers also have the limitation that only 65,536 items (bytes, words, doublewords) can be transferred in a single DMA operation. There, you'll find DMA controllers that can be set up to do "ping pong" transfers--do a transfer on one channel, then switch to a second channel, then back to the first one and so on. Takes a little bookkeeping, but there's no practical limit there.
 
That's only true for 8 bit DMA. 16-bit DMA can't cross 128K physical boundaries. Basically, both in XT and AT, the upper bits of an address are held in an addressable latch, which doesn't change during a DMA transfer. 16 bit DMA shifts the 8237 address one bit to the left, so you get what is, in effect a 17 bit byte address. Of course, you have to start the transfer on an even (word) boundary.

True enough; since the OP specified there being "16" possible segments and the 64K size specifically I assumed they were talking about XT DMA, but given that 16 bit DMA is certainly a thing used for sound cards, etc, it's good to be aware of the slightly different limitations on the 16 bit flavor. (Of course another XT/AT difference is the page register is a full 8 bits wide on an AT but only 4 bits on the XT, reflecting their 16MB vs 1MB addressing limits.)
 
Okay, thanks guys.

So, as a strategy, if I allocate a block of memory, I can check if that block falls within the limit of a physical page size and when not, I allocate another block until I find one that does. Would that work?

Another option I guess could be to allocate double the size of what I actually need, e.g, if I need a 2k buffer then simply allocate 4k, then within that buffer find the offset where a page boundry is crossed if any (e.g. 0xffff - offset > buffer_length), then use the offset within that buffer to program the DMA controller.

Or, yet another option, when in text mode use the 0xA000 physical page or when in graphics mode use the 0xB000 physical page... Maybe not such a good idea... :)
 
That second one is what I do in my diskette I/O routines. Allocate a buffer of twice what you need, find out where the physical "break" is, then return any memory (realloc) that you don't need.
 
In practical terms regarding the Sound Blaster, AFAIK (please, correct me if I'm wrong) you can send a sample up to 16kb to the DMA and it will be played on the background without needing anything else and/or without doing complex things like reserving a 64kb segment that corresponds to a physical one. But if you want to play bigger samples, you will have to implement a buffer polling system based on interrupts. That's why Sound Blaster asks to set an IRQ number (usually IRQ 5 or 7 for 8 bits). On your program, you have to build an interrupt handler that feeds the DMA when each 16kb chunk have finished sounding. This way you can send samples larger than 16kb, and also probably (I've never tested) more than 64kb, at the price of having to have a buffer on system RAM. In the game project I'm doing since several months ago, I go quite tight of RAM, so I'm trying to avoid sending >16kb samples to the SB to save those 32kb. Creative's CT-VOICE driver does it.

This C code by Root42 (the author of the Let's Code MS-DOS series on Youtube) does everything is needed: it creates a 32kb system RAM buffer in order to send big samples to the SB DMA, and does everything else needed to manage the Sound Blaster:

https://gist.github.com/root42/3ad5adb22e0bddd40fdafe9876cf9c3d
 
The point of the discussion is that any DMA transfer of any size cannot cross a 64K physical boundary.

At least that's the way I understand the discussion.
 
@Chuck(G) Yes, that is correct.

However, as in the link pointed out by @carlos12, you can see how root42 does the DMA buffer allocation.


He first allocates 32k, then calculates the physlical/linear address, grabs the "page" of that linear address, adds the length of the buffer to it and checks if the new linear address is still in the same page. If that is the case, voila and done, otherwise the assumption is that the next 32k allocated will be within a physical page boundary (not sure if you can actually trust that since memory might already be fragmented, but it might work for most scenarios I guess).

Thanks for the disucssion everyone, I think I am on the right track now :)
 
Yep, understood, no one disagrees with you. To me allocating double the size seems to be the way to go as well. (y)
 
Sound Blaster can handle 64K single shot DMA transfers, idk where the 16K misconception comes from.
 
I do like this in my player: (2 Buffer and select the correct one for replay. Then, use the other for mixing)

Code:
With MMSS_CFG^ do
  Begin
  If (4096-(PSegment_Tampon1 MOD 4096)) < (Max_Buffer_Samples_Nb) DIV 16 Then  { Max Buffers size}
      Begin
       Buffer_Start_Offs:=(Volume_Table_Size)+Max_Buffer_Samples_Nb;   { Cross Boundary -> Use 2nd part         }
       Left_Buffer_Offs:=(Volume_Table_Size)                           { First part for Stereo Mix Temp Buffer  }
      End
     Else
      Begin
      Buffer_Start_Offs:=(Volume_Table_Size);                        { Ok             -> Use First part    }
      Left_Buffer_Offs:=(Volume_Table_Size)+Max_Buffer_Samples_Nb;   { 2nd part for Stereo Mix Temp Buffer }
      End;
  Right_Buffer_Offs:=Left_Buffer_Offs+(Max_Buffer_Samples_Nb DIV 2);
  Seg_Table_Volume:=Seg(Tables_Modm^)
  End;
 
Back
Top