ARM Technical Support Knowledge Articles

Writing interrupt handlers for v6/v7 cores

Applies to: DS-5, RealView Development Suite (RVDS)



The ARM Compiler provides the “__irq” keyword for writing interrupt handlers in C. However, this is only designed for simple (non re-entrant) interrupt handlers. This is because it does not store all of the state information required for re-entrant interrupts.

The level of complexity of the top-level interrupt handler depends on whether it needs to call subroutines (using the branch with link, BL, instruction) and whether it needs to be re-entrant. In a typical system, there may be several interrupt sources with different priorities, connected via the IRQ input. A re-entrant interrupt handler allows a higher priority IRQ to interrupt the handling of a lower-priority IRQ. A re-entrant interrupt handler (at least its top-level) must be written in assembly.

Solution for a simple interrupt handler

To allow subroutines to be called from the top-level handler, the return address stored in the link register for IRQ mode (LR_irq) must be stacked before the BL call and restored afterwards, otherwise the BL will corrupt the return address of the interrupt handler by overwriting it with the return address of the subroutine. Also, registers r0-r3 and r12 are not preserved by the function callee (as specified by the AAPCS), so they must be preserved by the top-level handler (caller).

The above required functionality can be dealt with by decorating the IRQ handler function with the __irq keyword. For example:

__irq void IRQHandler (void)
    volatile unsigned int *base = (unsigned int *) 0x80000000;
    if (*base == 1)       // which interrupt was it?
        C_int_handler();  // process the interrupt
    *(base+1) = *base;    // clear the interrupt

Compiling with “armcc –cpu 7” gives the below code output:

    PUSH     {r0-r5,r12,lr}
    MOV      r4,#0x80000000
    LDR      r0,[r4,#0]
    CMP      r0,#1
    BNE      {pc}+0x6
    BL       C_int_handler
    LDR      r0,[r4,#0]
    STR      r0,[r4,#4]
    POP      {r0-r5,r12,lr}
    SUBS     pc,lr,#4     

It is important to clear the source of the interrupt within the interrupt handler, otherwise the current pending interrupt will be taken again immediately after returning from the interrupt handler, creating an endless loop. This is typically done by accessing an “interrupt acknowledged” register in the interrupt controller.

The IRQHandler C function and the C_int_handler() may be in ARM or Thumb state. The linker can change BL to BLX to change state if necessary.

Solution for a re-entrant interrupt handler

Re-entrant interrupt handlers, however, are more complex.

Consider a situation where an interrupt handler re-enables interrupts before calling a subroutine using a BL instruction. If another interrupt occurs during the execution of this subroutine, the return address of the subroutine (which was stored in LR_irq at function entry) will be corrupted when the second IRQ exception is taken.

How do we solve this problem?

Before the BL to the subroutine/C function, we should switch to another mode, typically Supervisor (SVC) mode by changing the CPSR (SVC mode is the mode that an Operating System typically runs in and, as such, can arrange for a reliable amount of available stack space). We can then push the SVC mode link register (LR_svc) onto the SVC mode stack, before re-enabling interrupts. It is then safe to call any subroutines using a BL instruction. On return from the subroutine, we can disable the interrupt, restore LR_svc and return from exception by restoring CPSR_irq and LR_irq from the SVC mode stack.

The above process consists of the following steps. On entry to the IRQ exception handler:

  1. adjust/save LR_irq and SPSR_irq onto SVC mode stack
  2. switch to SVC mode
  3. store AAPCS corruptible registers
  4. adjust stack to ensure 8-byte stack alignment
  5. store LR_svc
  6. clear the source of the interrupt
  7. re-enable interrupt
  8. call 2nd level C handler
  9. disable interrupt
  10. restore LR_svc
  11. un-adjust stack
  12. restore AAPCS registers
  13. return from the SVC mode stack

This can be written in assembly as follows:

    SUB     lr, lr, #4                ; Use SRS to save LR_irq and SPSP_irq
    SRSFD   sp!, #0x13                ;   on to the SVC mode stack

    CPS     #0x13                     ; Switch to SVC mode
    PUSH    {r0-r3, r12}              ; Store AAPCS regs on to SVC stack
    MOV     r1, sp
    AND     r1, r1, #4                ; Ensure 8-byte stack alignment…
    SUB     sp, sp, r1                ; …adjust stack as necessary
    PUSH    {r1, lr}                  ; Store adjustment and LR_svc

    BL      identify_and_clear_source ; Clear IRQ source
    CPSIE   i                         ; Enable IRQ
    BL      C_irq_handler             ; Branch to 2nd level handler
    CPSID   i                         ; Disable IRQ

    POP     {r1, lr}                  ; Restore LR_svc
    ADD     sp, sp, r1                ; Un-adjust stack

    POP     {r0-r3, r12}              ; Restore AAPCS registers
    RFEFD   sp!                       ; Return from the SVC mode stack

Again, C_irq_handler() may be in ARM or Thumb state. The linker can change BL to BLX to change state if necessary.

In the above example, please note:

Article last edited on: 2012-03-22 11:30:12

Rate this article

Disagree? Move your mouse over the bar and click

Did you find this article helpful? Yes No

How can we improve this article?

Link to this article
Copyright © 2011 ARM Limited. All rights reserved. External (Open), Non-Confidential