Posted By: Cim (Cim) on 'CZprogram' Title: Re: DMA Date: Fri May 15 21:12:08 1998 > Ludia, to naozaj nikto nema ani bajt textu o programovani DMA z pamaeti do > pamaeti ?? > Ved by to mala byt len parbajtova rutinka, no mne to ani zanic nechce > fungovat.. > Dakujem za hocico....BYe !! ^^^^^^ rekl sis o to, prikladam dva textaky o tomhle problemu, sam sem nikdy nenasel odvahu a cas poradne to vyzkouset. ----------------------------------------------------------------------- What is the DMA? The DMA is another chip on your motherboard (usually is an Intel 8237 chip) that allows you (the programmer) to offload data transfers between I/O boards. DMA actually stands for 'Direct Memory Access'. An example of DMA usage would be the Sound Blaster's ability to play samples in the background. The CPU sets up the sound card and the DMA. When the DMA is told to 'go', it simply shovels the data from RAM to the card. Since this is done off-CPU, the CPU can do other things while the data is being transferred. Enough basics. Here's how you program the DMA chip. DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD D 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 3 3000:0000 - 3000:FFFF 4 4000:0000 - 4000:FFFF 5 5000:0000 - 5000:FFFF 6 6000:0000 - 6000:FFFF 7 7000:0000 - 7000:FFFF 8 8000:0000 - 8000:FFFF 9 9000:0000 - 9000:FFFF A A000:0000 - A000:FFFF B B000:0000 - B000:FFFF C C000:0000 - C000:FFFF D D000:0000 - D000:FFFF E E000:0000 - E000:FFFF F F000:0000 - F000:FFFF This might look a bit overwhelming. Not to worry if you're a C programmer, as I'm going to assume you know the C language for the examples in this text. All the code in here will compile with Turbo C 2.0. 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. DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD D 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 DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD 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): DDDDDDDDDDDDDDDDDDDD MSB LSB x x x x x x x x DDDDDDDDDADDDDDDDDD A DDADD ~ ~ ~ 00 - Select channel 0 mask bit ~ ~ ADDDD 01 - Select channel 1 mask bit ~ ~ 10 - Select channel 2 mask bit ~ ~ 11 - Select channel 3 mask bit ~ ~ ~ ADDDDDDDDDD 0 - Clear mask bit ~ 1 - Set mask bit ~ ADDDDDDDDDDDDDDDDDDDDDDD xx - Don't care Mode Register (0Bh): DDDDDDDDDDDDDDDDDDDD MSB LSB x x x x x x x x DDADD A A DDADD DDADD ~ ~ ~ ~ ~ 00 - Channel 0 select ~ ~ ~ ~ ADDDD 01 - Channel 1 select ~ ~ ~ ~ 10 - Channel 2 select ~ ~ ~ ~ 11 - Channel 3 select ~ ~ ~ ~ ~ ~ ~ ~ 00 - Verify transfer ~ ~ ~ ADDDDDDDDDDDD 01 - Write transfer ~ ~ ~ 10 - Read transfer ~ ~ ~ ~ ~ ADDDDDDDDDDDDDDDDDDDD 0 - Autoinitialized ~ ~ 1 - Non-autoinitialized ~ ~ ~ ADDDDDDDDDDDDDDDDDDDDDDDD 0 - Address increment select ~ ~ 00 - Demand mode ADDDDDDDDDDDDDDDDDDDDDDDDDDDDDD 01 - Single mode 10 - Block mode 11 - Cascade mode DMA clear selected channel (0Ch): DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD Outputting a zero to this port stops all DMA processes that are currently happening as selected by the mask register (0Ah). DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD D 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): DDDDDDDDDDDDDDDDDDDD MSB LSB x x x x x x x x DDDDDDDDDADDDDDDDDD A DDADD ~ ~ ~ 00 - Select channel 4 mask bit ~ ~ ADDDD 01 - Select channel 5 mask bit ~ ~ 10 - Select channel 6 mask bit ~ ~ 11 - Select channel 7 mask bit ~ ~ ~ ADDDDDDDDDD 0 - Clear mask bit ~ 1 - Set mask bit ~ ADDDDDDDDDDDDDDDDDDDDDDD xx - Don't care Mode Register (D6h): DDDDDDDDDDDDDDDDDDDD MSB LSB x x x x x x x x DDADD A A DDADD DDADD ~ ~ ~ ~ ~ 00 - Channel 4 select ~ ~ ~ ~ ADDDD 01 - Channel 5 select ~ ~ ~ ~ 10 - Channel 6 select ~ ~ ~ ~ 11 - Channel 7 select ~ ~ ~ ~ ~ ~ ~ ~ 00 - Verify transfer ~ ~ ~ ADDDDDDDDDDDD 01 - Write transfer ~ ~ ~ 10 - Read transfer ~ ~ ~ ~ ~ ADDDDDDDDDDDDDDDDDDDD 0 - Autoinitialized ~ ~ 1 - Non-autoinitialized ~ ~ ~ ADDDDDDDDDDDDDDDDDDDDDDDD 0 - Address increment select ~ ~ 00 - Demand mode ADDDDDDDDDDDDDDDDDDDDDDDDDDDDDD 01 - Single mode 10 - Block mode 11 - Cascade mode DMA clear selected channel (D8h): DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD 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. DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD D /* 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; } DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD D 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 ---------------------------------------------------------------------------- DMA Transfers for FAST Memory Moves If you ever have the need for super fast moves of blocks of memory and you can put up with a lot of restrictions in order to gain the speed there is a method in the PC environment to move bytes at the rate of one every 3 clock cycles. Compare that to the clocks for a rep movsb and then say that isn't fast. This isn't for every programmer and it certainly isn't for every time you need to move a few bytes. The DMA chip is a four channel device. Channel 0 is reserved for refresh of memory that requires at least 256 consecutive bytes be read some- where in memory at intervals less than about 4ms. The other three channels are available for such things as transferring information to and from the disk drives or the ports. Interesting trivia so far, but not very useful in moving memory around. It gets worse. The 8259 DMA doesn't know anything about segments. It only knows a 64k universe. This is where the Page registers come in. The page registers decide which page (on 64k boundaries) the 8259 will look at for any operation. There are not, as you might guess, 4 page registers,but only 2 plus a default. If it is not channel 1 or 2 then it uses the default register programmed as channel 3. A careful reading of the data sheet of the 8259 discloses that it is capable of doing a memory to memory transfer but only between channels 0 and 1. That is why this method is a little tricky to use. In order to set up your own parameters you have to disable the timer from asking for a DMA from chan- nel 0 every x milliseconds and reconfigure the 8259 and assume the respon- sibility for doing the memory refresh. It actually sounds worse than it is. The configuring and re configuring of the 8259 doesn't take all that long, so the time is made up after only moving a few tens of bytes, and if you move at least 256 CONSECUTIVE bytes the memory refresh requirement is met for another 2 or 3 milliseconds. The page registers are taken care of by setting channels 1 and 3 to the same value. Given below is an example of a program I wrote just to test the idea. A lot of the setup is too complex to explain in this short article, but if you are interested in checking it all out you will need a data sheet on the 8259. This worked nicely on my machine and should on most compatibles just the way it is. With the not-so-compatible it may very well not. I hope this listing is well enough commented so you can figure it out and make use of it sometime. ` DMA SOURCE PAGE 60,132 TITLE DMA MEMORY TO MEMORY DMA EQU 0 STACK SEGMENT PUBLIC 'STACK' DB 32 DUP('STACK') STACK ENDS DATA SEGMENT PUBLIC 'DATA' SOURCE DW 08000H TARGET DW 09000H NUMBER DW 800H INCDEC DB 0 PAGER DB 0BH ;PAGE (O TO F) FILL DB 0 ;2 IF A FILL OP DATA ENDS ASSUME CS:CODE,DS:DATA,ES:DATA CODE SEGMENT PUBLIC 'CODE' START: MOV AX,DATA MOV DS,AX MOV AX,0B800H MOV ES,AX PAGE: MOV AL,PAGER ;PAGE TO OPERATE IN OUT 83H,AL UNDMA: OUT 0DH,AL ;MASTER RESET OF DMA MOV DX,03D8H MOV AL,1 OUT DX,AL MOV AX,SOURCE ;WHERE IS IT COMING FROM OUT 0H,AL MOV AL,AH OUT 0H,AL MOV AX,TARGET ;WHERE TO PUT IT OUT 2H,AL MOV AL,AH OUT 2H,AL MOV AX,NUMBER ;HOW MANY OUT 3H,AL MOV AL,AH OUT 3H,AL MOV AL,009H ;ENABLE M TO M,COMPRESSED OR AL,FILL ;WILL BE 2 IF FILL OP OUT 8H,AL MOV AL,088H ;BLOCK MODE, INC, READ OR AL,INCDEC ;WILL BE 20H IF DEC OUT 0BH,AL MOV AL,85H ;BLOCK MODE, INC, WRITE OR AL,INCDEC ;WILL BE 20H IF DEC OUT 0BH,AL MOV AL,4 ;THIS IS THE REQUEST OUT 9,AL ;DO IT MOV AL,9 OUT DX,AL RESET: OUT 0DH,AL ;THIS IS A MASTER RESET OUT 0CH,AL ;RESET F/L F/F MOV AL,01 OUT 0,AL OUT 0,AL REINIT: MOV AL,0 OUT 83H,AL ;MOVES REFRESH TO BASE PAGE MOV AL,0FFH OUT 1,AL PUSH AX OUT 1,AL OUT 0BH,AL INC AL ;MODE CHAN3^L OUT 0BH,AL PUSH AX POP AX POP AX POP AX MOV AH,4CH INT 21H CODE ENDS END START --------------------------------------------------------------------------- mozna, kdybys napsal svuj e-mail, bylo by to jednodussi, ale to je jedno. Snad ti to pomuze. Cim