Nios® V/II Embedded Design Suite (EDS)
Support for Embedded Development Tools, Processors (SoCs and Nios® V/II processor), Embedded Development Suites (EDSs), Boot and Configuration, Operating Systems, C and C++
12608 Discussions

DMA transfer inside ISR: Problem

Altera_Forum
Honored Contributor II
4,808 Views

Hi everybody, 

 

I think this is a common issue, but I didn't find a solution to my question, so hopefully I will get an answer here: 

 

I have a DMA controller which I want to use on an IRQ coming from a PIO. 

 

The DMA works fine and also the IRQ does.  

But when I try to call the DMA transfer function INSIDE the ISR from the PIO, the DMA doesn't send it's Interrupt at the end (when transfer is finished). 

 

Priority of PIO Interrupt is 7, Priority of DMA is 6 (so there should not be a priority problem). 

 

Does anyone of you know a solution to my problem? 

 

Here is my code: 

 

volatile static int rx_done = 0; static void done_DMA(void* handle, void* data) { rx_done = 1; } void measureIOs() { int rc; alt_dma_txchan txchan; alt_dma_rxchan rxchan; unsigned long* tx_data = (void*) EXT_DPR_TRISTATE_INTERFACE_0_BASE; // pointer to data to send unsigned long* rx_buffer = (void*) SDRAM_BASE + 0x100000; // pointer to rx buffer rx_buffer = (void*) SDRAM_BASE + 0x100000; rx_done = 0; // Create the transmit channel if ((txchan = alt_dma_txchan_open("/dev/dma_0")) == NULL) { printf ("Failed to open transmit channel\n"); exit (1); } // Create the receive channel if ((rxchan = alt_dma_rxchan_open("/dev/dma_0")) == NULL) { printf ("Failed to open receive channel\n"); exit (1); } if(alt_dma_txchan_ioctl(txchan,ALT_DMA_SET_MODE_32,NULL)<0) { exit(1); } if(alt_dma_rxchan_ioctl(rxchan,ALT_DMA_SET_MODE_32,NULL)<0) { exit(1); } if(alt_dma_txchan_ioctl(txchan,ALT_DMA_TX_ONLY_OFF,NULL)<0) { exit(1); } if(alt_dma_rxchan_ioctl(rxchan,ALT_DMA_RX_ONLY_OFF,NULL)<0) { exit(1); } // Post the transmit request if ((rc = alt_dma_txchan_send (txchan,tx_data,4096,NULL,NULL)) < 0) { printf ("Failed to post transmit request, reason = %i\n", rc); exit (1); } // Post the receive request if ((rc = alt_dma_rxchan_prepare (rxchan,rx_buffer,4096,done_DMA,NULL)) < 0) { printf ("Failed to post read request, reason = %i\n", rc); exit (1); } // wait for transfer to complete while (!rx_done); printf ("Transfer successful!\n"); } /* INTERRUPT VARIABLE DEFINITONS */ volatile int edge_capture; /* **************** INTERRUPT FUNCTIONS ********************* */ # ifdef PIO_SELECTED_RAM_BASE # ifdef ALT_ENHANCED_INTERRUPT_API_PRESENT static void handle_ram_interrupt(void* context) # else static void handle_ram_interrupt(void* context, alt_u32 id) # endif { /* Cast context to edge_capture's type. It is important that this be * declared volatile to avoid unwanted compiler optimization. */ volatile int* edge_capture_ptr = (volatile int*) context; /* Store the value in the Button's edge capture register in *context. */ *edge_capture_ptr = IORD_ALTERA_AVALON_PIO_EDGE_CAP(PIO_SELECTED_RAM_BASE); /* Reset the Button's edge capture register. */ IOWR_ALTERA_AVALON_PIO_EDGE_CAP(PIO_SELECTED_RAM_BASE, 0); measureIOs(); /* * Read the PIO to delay ISR exit. This is done to prevent a spurious * interrupt in systems with high procekssor -> pio latency and fast * interrupts. */ IORD_ALTERA_AVALON_PIO_EDGE_CAP(PIO_SELECTED_RAM_BASE); } /* Initialize the ram_pio. */ static void init_ram_pio() { /* Recast the edge_capture pointer to match the alt_irq_register() function * prototype. */ void* edge_capture_ptr = (void*) &edge_capture; /* Enable all 4 button interrupts. */ IOWR_ALTERA_AVALON_PIO_IRQ_MASK(PIO_SELECTED_RAM_BASE, 0x1); /* Reset the edge capture register. */ IOWR_ALTERA_AVALON_PIO_EDGE_CAP(PIO_SELECTED_RAM_BASE, 0x0); /* Register the interrupt handler. */ # ifdef ALT_ENHANCED_INTERRUPT_API_PRESENT alt_ic_isr_register(PIO_SELECTED_RAM_IRQ_INTERRUPT_CONTROLLER_ID,PIO_SELECTED_RAM_IRQ, handle_ram_interrupt, edge_capture_ptr, 0x0); # else alt_irq_register( PIO_SELECTED_RAM_IRQ, edge_capture_ptr, handle_ram_interrupt); # endif } # endif /***************** PIO_SELECTED_RAM_BASE **********************/ int main() { printf("Hello from Nios II!\n"); init_ram_pio(); while(1); return 0; }  

 

 

 

 

Thanks for all answers ! =)
0 Kudos
23 Replies
Altera_Forum
Honored Contributor II
1,754 Views

I would set some flag inside the ISR and call up the DMA from outside the ISR based on this flag. Interrupts should be short and by placing a DMA transfer call inside of it there are a lot of things that could go wrong (too many to list). I suspect interrupts are masked while you are in the PIO ISR and that's why you don't see the DMA IRQ fire (it fires but nobody is listening).

0 Kudos
Altera_Forum
Honored Contributor II
1,754 Views

Hi, 

 

Are you using the ENHANCED Interrupt API or the LEGACY (old one)? 

Are you using an EIC(external interrupt controller) or IIC (internal interrupt controller)? 

 

Maybe the problem is that your PIO-ISR isn't INTERRUPTIBLE.  

At least with the LEGACY API an ISR (even if it is low priority) couldn't be interrupted unless you have told the system to do it. 

 

So in your case, you are waiting inside the PIO-ISR for the DMA to interrupt. But for the LEGACY API (maybe for the ENHANCED with IIC too) it is not possible, because you didn't declare the ISR as interruptible. 

 

With LEGACY API you can do it by calling alt_irq_interruptible() at the beginning of your PIO-ISR and alt_irq_non_interruptible() at the end. Now it should be interruptible for IRQs with higher priority. 

 

I don't know if there are any similar functions for the ENHANCED API using IIC. Didn't find any yet. But maybe you don't need to, I am not sure.... 

 

Regards 

Philipp
0 Kudos
Altera_Forum
Honored Contributor II
1,754 Views

Hi all, 

 

thank you for your posts. 

 

@Omen: My operation is quite time-critical this is why I think a polling solution wouldn't be the best in my case. 

 

@pillemann: 

 

Im using IIC with the Enhanced API. I tried to use the Legacy API with the alt_irq_interruptible() function and it worked in a HelloWorld project!  

 

I still face problems when using the code in uCOS/II environment building up on "simple socket server". It works, but after the first call of the ISR it doesn't return to idle mode and hangs at some place.  

 

I cannot use both the legacy and enhanced API, can I ? I believe that maybe some Nichestack function accesses the enhanced API. Could this be the problem ?
0 Kudos
Altera_Forum
Honored Contributor II
1,754 Views

 

--- Quote Start ---  

 

I still face problems when using the code in uCOS/II environment building up on "simple socket server". It works, but after the first call of the ISR it doesn't return to idle mode and hangs at some place.  

 

--- Quote End ---  

Which API is used for uCOS/II environment? And what do you mean with "doesn't return to idle mode"? 

 

 

--- Quote Start ---  

 

I cannot use both the legacy and enhanced API, can I ? 

--- Quote End ---  

Don't think so. 

 

 

But one more question. Why do you wait for the DMA-Transfer to be completed inside the ISR? 

The advantage of the DMA is that the CPU is not occupied during DMA-Transfer (even if 4096*4Bytes is not that much, it's still not only a few bytes).  

Why don't you just start the DMA-Transfer inside the PIO-ISR? If the DMA is completed it will assert the IRQ and DMA-ISR is called. 

 

ADDITION: In the API Reference it is mentioned that alt_dma_txchan_open() should not be called from ISRs! Further I don't see any call to alt_dma_txchan_close(). Maybe this leads to problems if you want to initiate your second transfer. 

 

We don't use the HAL API for DMA-Transfers. I think it is better if you use directly the REG-File for setting up DMA-Transfers inside ISRs. (Faster and you got the control over DMA) 

There are some walls to take but afterwars it works fine.... 

 

Regards
0 Kudos
Altera_Forum
Honored Contributor II
1,754 Views

Hi! 

 

I want to use the DMA driver for a UART -> SRAM transfer. When the stansfer finished i will to generate a IRQ (using the DMA registers). I register the dma irq with the function (I use EIC and DMA_IRQ has the highest priority): 

 

alt_ic_isr_register( DMA_IRQ_INTERRUPT_CONTROLLER_ID,  

DMA_IRQ, 

DMA_isr, 

DMA_BASE,  

NULL); 

 

The transfer works (the lenght decrese), but when the DMA_isr is called the program crashed. Can you help me?! How can I register the DMA IRQ?  

Thanks!
0 Kudos
Altera_Forum
Honored Contributor II
1,754 Views

Hi, 

 

 

--- Quote Start ---  

alt_ic_isr_register( DMA_IRQ_INTERRUPT_CONTROLLER_ID,  

DMA_IRQ, 

DMA_isr, 

DMA_BASE,  

NULL); 

--- Quote End ---  

I guess DMA_IRQ_INTERRUPT_CONTROLLER_ID is the id of your VIC?  

Why do you use DMA_BASE as isr_context? Don't do that. DMA_BASE is not a memory region. 

Have you maybe enabled a seperate exception stack? 

 

Regards
0 Kudos
Altera_Forum
Honored Contributor II
1,754 Views

Hi! 

Thanks for reply! 

 

Yes, DMA_IRQ_INTERRUPT_CONTROLLER_ID is the id of my VIC - is 0 because in my project i have only 1 VIC - from system.h:# define DMA_BASE 0x2903440# define DMA_IRQ 0# define DMA_IRQ_INTERRUPT_CONTROLLER_ID 0 

 

If I don't use the isr_context i have the same result....when the irq is generated the program crashed! I try to test te program with a interrupt from the UART and it works fine. If I use the HAL driver it also works fine; but I will read the status register of DMA during the ISR to detect if IRQ is generated by a EOP or length = 0. If I use the HAL driver and I read the status register in the ISR, the status register is always 0, so i will write my driver. My test code is simple: 

 

in main: 

 

IOWR_ALTERA_AVALON_UART_DIVISOR(SERIAL_UART_BASE,108); // Baud Rate: 921600 @ 100Mhz 

IOWR_ALTERA_AVALON_UART_EOP(SERIAL_UART_BASE,'\r'); // Serial Uart EOP: '\r 

 

alt_u8* buffer; 

 

alt_ic_isr_register( DMA_IRQ_INTERRUPT_CONTROLLER_ID,  

DMA_IRQ, 

DMA_isr, 

NULL,  

NULL); 

 

 

IOWR_ALTERA_AVALON_DMA_STATUS(DMA_BASE, 0); 

IOWR_ALTERA_AVALON_DMA_RADDRESS(DMA_BASE, SERIAL_UART_BASE); 

IOWR_ALTERA_AVALON_DMA_WADDRESS(DMA_BASE, buffer); 

IOWR_ALTERA_AVALON_DMA_LENGTH(DMA_BASE, 5); // transfer length = 5 

IOWR_ALTERA_AVALON_DMA_CONTROL(DMA_BASE, 0x1F9); // enable IRQ -set single transfer lenght @ 8 bits (1 char) - enable transfer 

 

while (1) { 

usleep(1000000); 

printf("length: 0x%X\n",IORD_ALTERA_AVALON_DMA_LENGTH(DMA_BASE)); //to test if DMA recive data (for every recived char the length decrease - it works fine) 

 

 

and this is the ISR routine: 

 

static void DMA_isr (void* handle) { 

printf("IRQ \n"); 

 

The result is that when the program is running the length decrease an wher the length = 0 or the EOP \r is recived the program crash! 

 

No, I don't enable a seperate exception stack. (look hal.jpg) 

 

Regards
0 Kudos
Altera_Forum
Honored Contributor II
1,754 Views

Hi, 

 

What exactly do you mean with crashing? Can you describe the behaviour a bit more. 

 

The code you posted looks not too bad at all. 

 

But two things I would change/add.  

 

1. Don't use printf in your ISR. I would use a volatile variable like isr_done to check if the ISR was called or not. 

2. At the beginning of the ISR I would clear the status reg of the DMA with IOWR_ALTERA_AVALON_DMA_STATUS(DMA_BASE, 0); 

 

Question: Have you enabled the burst mode option for DMA in SOPC? I never used this option but I read a post where somebody had a problem with it.
0 Kudos
Altera_Forum
Honored Contributor II
1,754 Views

Hi Pilleman! 

Thank you very much! With your suggestion the DMA works!  

But the behaviour of the program is so strange: 

 

- if I declare alt_u8* buffer in the main, I read teh correct typed values from the buffer - with the command printf("addr(0x%X) = %c \n", &buffer[0], (char) buffer[0]); 

 

- if I declare buffer with malloc: alt_u8 *buffer = malloc(BUFF_SIZE * sizeof (alt_u8)); (I change only alt_u8* buffer with this line) - I read wrong values from the buffer 

 

-if I delcare alt_u8* buffer as a global variable - I read the wrong values from buffer 

 

- if I use alt_u8 buffer[BUFF_SIZE] instead of alt_u8 *buffer - I read the wrong values from buffer 

 

Why?! My DMA don't use the Burst and I use Quartus 9.1 sp2 and Nios SBT 9.1. It's possible that it's a compiler problem?
0 Kudos
Altera_Forum
Honored Contributor II
1,754 Views

Hi! 

 

Well great that it works. 

 

 

--- Quote Start ---  

- if I declare alt_u8* buffer in the main, I read teh correct typed values from the buffer - with the command printf("addr(0x%X) = %c \n", &buffer[0], (char) buffer[0]); 

 

- if I declare buffer with malloc: alt_u8 *buffer = malloc(BUFF_SIZE * sizeof (alt_u8)); (I change only alt_u8* buffer with this line) - I read wrong values from the buffer 

 

-if I delcare alt_u8* buffer as a global variable - I read the wrong values from buffer 

 

- if I use alt_u8 buffer[BUFF_SIZE] instead of alt_u8 *buffer - I read the wrong values from buffer 

 

Why?! My DMA don't use the Burst and I use Quartus 9.1 sp2 and Nios SBT 9.1. It's possible that it's a compiler problem?  

--- Quote End ---  

 

 

Do you have a cache in your CPU?  

If so, the behaviour could be related to the cache, because you might be reading the values stored in the cache and not the actual data which are updated by the DMA. 

 

Greets
0 Kudos
Altera_Forum
Honored Contributor II
1,754 Views

Hi! Thank you so much for your fast response! 

Yes, in my system I have a processor NiosII/f with data and istruction cache (look the attachment). In my system I use an external SRAM and a little on_chip_memory (4096 Bytes) which is used also for exception vector. If I read the address of buffer it seems that it's always allocated in external SRAM in all previous cases. 

How can I bypass the cache and read the values direct from the ram?! 

Thanks for your help!  

 

 

 

(http://www.answerbag.com/q_view/1182695#ixzz13xxzrkvj)
0 Kudos
Altera_Forum
Honored Contributor II
1,754 Views

You are welcome! 

 

For example you have the following possibilities for bypassing the cache. 

 

1. You can use the IOWR/IORD(or the 16/8Bit versions)-Macros for direct memory access of your data. But in this case you have always to use IORD/IOWR to read/write valid data. The data will still be cached but as long as you use IORD/IOWR everything should work fine. 

 

2. Use alt_uncached_malloc() / alt_uncached_free() . In this case you will get a pointer to data which will not be cached. (Remark: Bit32 of your pointer will be set to indicate that it is uncached) 

 

I have already used both. With Alternative 2 you can more easily access your memory as you were used to. But you have to ensure that you free the memory after you don't need it anymore. 

 

Regards
0 Kudos
Altera_Forum
Honored Contributor II
1,754 Views

For (1) you need to ensure that the cache line is invalid at the start. 

For (2) you need to ensure that the ends of the memory block aren't shared with anything else.
0 Kudos
Altera_Forum
Honored Contributor II
1,754 Views

Thanks very much! 

Both the solutions work!  

Thanks for your support and have a nice day! 

 

Regards
0 Kudos
Altera_Forum
Honored Contributor II
1,754 Views

Hi DSL! 

 

 

--- Quote Start ---  

For (1) you need to ensure that the cache line is invalid at the start. 

For (2) you need to ensure that the ends of the memory block aren't shared with anything else. 

--- Quote End ---  

 

 

For (1): why the cache line have to be INVALID (?!) at the start? What this means? And how I could test it? 

For (2): how can I ensure that the memory block aren't shared with anything else? This work is not made by the compiler? 

 

Regards
0 Kudos
Altera_Forum
Honored Contributor II
1,754 Views

Another question: 

 

I try also to transmit data from a variable (placed in the SRAM) to the UART with a DMA. I write a easy test software, but the result is strange. This is the code: 

 

printf("Hello from DMA?!\n"); 

IOWR_ALTERA_AVALON_UART_DIVISOR(SERIAL_UART_BASE,108); // Baud Rate: 921600 @ 100Mhz 

 

alt_u8 *tx_buffer = alt_uncached_malloc(BUFF_SIZE * sizeof (alt_u8)); 

tx_buffer = "Hello World\0"; 

 

printf("tx buffer position: 0x%X \n",tx_buffer); 

 

IOWR_ALTERA_AVALON_DMA_STATUS(DMA_BASE, 0); 

IOWR_ALTERA_AVALON_DMA_RADDRESS(DMA_BASE, tx_buffer); 

IOWR_ALTERA_AVALON_DMA_WADDRESS(DMA_BASE, SERIAL_UART_BASE+1); // TxData address is @ SERIAL_UART_BASE + 1(offset) 

IOWR_ALTERA_AVALON_DMA_LENGTH(DMA_BASE, 12); 

IOWR_ALTERA_AVALON_DMA_CONTROL(DMA_BASE, 0x289); 

 

usleep (500000); 

printf("status: 0x%X \n",IORD_ALTERA_AVALON_DMA_STATUS(DMA_BASE)); 

printf("read add: 0x%X \n",IORD_ALTERA_AVALON_DMA_RADDRESS(DMA_BASE)); 

printf("write add: 0x%X \n",IORD_ALTERA_AVALON_DMA_WADDRESS(DMA_BASE)); 

printf("length: 0x%X \n",IORD_ALTERA_AVALON_DMA_LENGTH(DMA_BASE)); 

printf("control: 0x%X \n",IORD_ALTERA_AVALON_DMA_CONTROL(DMA_BASE)); 

 

The DMA write 2* '\0' chars and 3* 'H' chars (look the attachment). Why? 

The console output is right: 

 

Hello from DMA?! 

tx buffer position: 0x288F638  

status: 0x11 // Operation Done - len = 0 

read add: 0x288F644 // 0x288F638 + C(12) 

write add: 0x2903461  

length: 0x0  

control: 0x289 

 

Regards
0 Kudos
Altera_Forum
Honored Contributor II
1,754 Views

You are using Altera's UART? 

 

 

--- Quote Start ---  

IOWR_ALTERA_AVALON_DMA_WADDRESS(DMA_BASE, SERIAL_UART_BASE+1); // TxData address is @ SERIAL_UART_BASE + 1(offset) 

--- Quote End ---  

I think this is not right. Register offset is 1, but the width is 32 bit. So next register address should be BASE + 4. 

Try to use this: 

IOWR_ALTERA_AVALON_DMA_WADDRESS(DMA_BASE, IOADDR_ALTERA_AVALON_UART_TXDATA(SERIAL_UART_BASE)); 

 

Strange you see anything at all...... 

 

Regards
0 Kudos
Altera_Forum
Honored Contributor II
1,754 Views

Hi! 

Yes, I'm using Altera UART. I try to use IOADDR_ALTERA_AVALON_UART_TXDATA(SERIAL_UART_BASE), but with this command the UART doesn't work, so I try to use a different BASE and with SERIAL_UART_BASE + 1 it works. So strange... 

 

my SERIAL UART BASE is: 0x2903460  

SERIAL_UART_BASE + 1 give me the address: 0x2903461  

and IOADDR_ALTERA_AVALON_UART_TXDATA(SERIAL_UART_BASE) give me the address: 0x2903464
0 Kudos
Altera_Forum
Honored Contributor II
1,754 Views

 

--- Quote Start ---  

and IOADDR_ALTERA_AVALON_UART_TXDATA(SERIAL_UART_BASE) give me the address: 0x2903464 

--- Quote End ---  

That's the correct address. I am using the same for my DMA->UART transfer which works pretty fine. Take this address, because calling IOADDR_ALTERA_AVALON_UART_TXDATA(SERIAL_UART_BASE) gives you the correct address. 

 

 

--- Quote Start ---  

IOWR_ALTERA_AVALON_DMA_CONTROL(DMA_BASE, 0x289); 

--- Quote End ---  

I think you forgot to set I_EN in the control register? Set it and try again.
0 Kudos
Altera_Forum
Honored Contributor II
1,650 Views

Hi! 

No, I don't forget to set I_EN. I don't enable the IRQ because I'll not use a ISR routine in my project to transmit the data from DMA to UART.  

Which version of Quartus and Nios EDS do you use?  

 

Tomorrow I will install Quartus 10 and Nios EDS and I will recompile the project in the new version (now I used the 9.1 sp2). I hope that it’s a compiler error…. 

Thanks for your help!
0 Kudos
Reply