Forum Discussion
Altera_Forum
Honored Contributor
16 years agoDMA transfer inside ISR: Problem
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 ! =)23 Replies
- Altera_Forum
Honored Contributor
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).
- Altera_Forum
Honored Contributor
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 - Altera_Forum
Honored Contributor
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 ? - Altera_Forum
Honored Contributor
--- 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 - Altera_Forum
Honored Contributor
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! - Altera_Forum
Honored Contributor
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 - Altera_Forum
Honored Contributor
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 - Altera_Forum
Honored Contributor
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. - Altera_Forum
Honored Contributor
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? - Altera_Forum
Honored Contributor
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