Skip to content

Latest commit

 

History

History

nested_irq

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 

Nested IRQ project

This project tests using the GIC on the Cortex-A7 to respond to nested interrupts. A nested interrupt happens when an interrupt interrupts an interrupt. (Got that?) The interrupts should "nest", meaning that after the second one finishes being handled, it'll return to the first one. It's not a simple thing to implement as it must be done in assembly (the C compiler cannot generate the instructions needed to switch modes, nor insure proper stack alignment to allow SIMD usage in interrupts). And if not done properly everything will seem to work... except every once in a while a variable might change values mysteriously at random times, or you get DataAbort exceptions at odd times.

When an interrupt happens, the core will immediately start executing code from a specific place in the vector table. If you don't know about vector tables, it's the same concept as in the Cortex-M, and there is lots of documentation and tutorials online: try this guide. Cortex-M cores have large vector tables -- sometimes several hundred entries, one for each possible interrupt. Cortex-A cores hAve much smaller vector tables, just eight entries. One of these entries is the IRQ handler, which is called by the processor whenever any interrupt is triggered (actually there is another kind of interrupt called Fast or FIQ, but they behave the same as IRQ, so we'll not discuss them now). The vector table in our project has b IRQ_Handler at the entry for the IRQ handler, and we define the IRQ_Handler function in shared/drivers/irq_handler.cc. This function is written in assembly and is basically a dispatcher that gets called on every interrupt. It stores the current state (registers and status flags) and makes sure the stack is aligned properly. boundaries, reads the IRQ number, and jumps to the appropriate interrupt handler. When the interrupt handler returns, it tells the GIC we're done handling the interrupt, and restores the stack alignment, registers, and status flags.

Like the other projects in this repository, this is designed for the OSD32MP1 BRK board, but any STM32MP157-based board with header pins for UART4 exposed will suffice.

To use, first make sure u-boot is installed on an SD-card, as usual (see README in the project root directory).

Then, in this directory run:

make

In the build/ dir, you should see the a7-main.uimg file. Copy it to the 4th partition of the SD-card, as usualy (again, see README in the project root dir).

Reboot your board with a UART-to-USB cable connected, and watch u-boot's startup messages scroll by in a terminal.

ABC

Testing nested interrupts
Make sure this is compiled with -O1 to test register clobbering (Makefile line 4, OPTFLAG)

You should see steps 1-6 occur in order:
1) Triggering outer ISR: Red LED 1 turning off (PI8 rising edge)
 2) Entering outer ISR (red LED 1)
  3) Triggering Inner ISR: Green LED 1 turning off (PI9 rising edge)
   4) Entering inner ISR (green LED 1)
    5) Inner ISR: both sums are OK. ...Exiting inner ISR
     6) Outer ISR: both sums are OK. ...Exiting ISR
Reached main context: both sums are OK

That's it! Notice the inner ISR starts and stops before the outer ISR returns. This is the essential feature of nesting. The trick is that IRQ_Handler disables and enabled interrupts so that it can only be interrupted during the call the ISRHandler (and whatever function ISRHandler calls). ISRHandler is a normal function, so the compiler will automatically make sure that registers r4-r11 and r13 (stack pointer) are restored back to their original values when the ISRHandler returns to IRQ_Handler. Therefore IRQ_Handler only needs to stash r0-r3, r12, and lr, and the FPU registers (in addition to the address to return to and the orignal status flags the moment before we were interrupted).

A note on stack alignment: The ARM standard requires that the stack is aligned to 8-byte boundaries whenever a function starts or stops. There aren't any alignment requirements in the middle of a function, which is likely where we'll be when an interrupt is triggered. Since our interrupt handler is a function, we must check if the stack is aligned to 8-byte boundaries and fix it if it isn't aligned. Then, when the handler exits, we must restore the stack pointer exactly as it was before, so that the function that was interrupted acts the same as if it wasn't interrupted. What happens if we don't align the stack? Well, one thing that can happen is load/store instructions that operate on large chunks of data (8-bytes or more) can crash. Instructions like VLD1.64 requires that the source location is an 8-byte aligned address, which is often the stack pointer. This means our interrupt handler will produce a DataAbort, but only when it happened to get called when the stack was not on an 8-byte boundary.