Reentrant interrupt handlers

If an interrupt handler enables interrupts before calling a subroutine and another interrupt occurs, the return address of the subroutine stored in the IRQ mode LR is corrupted when the second IRQ is taken. This is because the processor automatically saves the return address into the IRQ mode LR for the new interrupt overwriting the return address for the subroutine. This results in an infinite loop when the subroutine in the original interrupt tries to return.

A reentrant interrupt handler must save the IRQ state, switch processor modes, and save the state for the new processor mode before branching to a nested subroutine or C function. It must also ensure that the stack is eight-byte aligned for the new processor mode before calling AAPCS-compliant compiled C code that might use LDRD or STRD instructions or eight-byte aligned stack-allocated data.

Using the __irq keyword in C does not cause the SPSR to be saved and restored, as required by reentrant interrupt handlers, so you must write your top level interrupt handler in assembly language.

In ARMv4 or later you can switch to System mode if you require privileged access.


This method works for both IRQ and FIQ interrupts. However, because FIQ interrupts are meant to be handled as quickly as possible there is normally only one interrupt source, so it might not be necessary to provide for reentrancy.

The steps required to enable interrupts safely in an IRQ handler are:

  1. Construct the return address and save it on the IRQ stack.

  2. Save the work registers, non callee-saved registers and IRQ mode SPSR.

  3. Clear the source of the interrupt.

  4. Switch to System mode, keeping IRQs disabled.

  5. Check that the stack is eight-byte aligned and adjust if necessary.

  6. Save the User mode LR and the adjustment, 0 or 4 for Architectures v4 or v5TE, used on the User mode SP.

  7. Enable interrupts and call the C interrupt handler function.

  8. When the C interrupt handler returns, disable interrupts.

  9. Restore the User mode LR and the stack adjustment value.

  10. Readjust the stack if necessary.

  11. Switch to IRQ mode.

  12. Restore other registers and IRQ mode SPSR.

  13. Return from the IRQ.

The following examples show how this works for System mode.

Example 37. Reentrant interrupt handler for ARMv4/v5TE

    IMPORT C_irq_handler
    IMPORT identify_and_clear_source

    SUB     lr, lr, #4             ; construct the return address
    PUSH    {lr}                   ; and push the adjusted lr_IRQ
    MRS     lr, SPSR               ; copy spsr_IRQ to lr
    PUSH    {R0-R4,R12,lr}         ; save AAPCS regs and spsr_IRQ
    BL      identify_and_clear_source
    MSR     CPSR_c, #0x9F          ; switch to SYS mode, IRQ is
                                   ; still disabled. USR mode
                                   ; registers are now current.
    AND     R1, sp, #4             ; test alignment of the stack
    SUB     sp, sp, R1             ; remove any misalignment (0 or 4)
    PUSH    {R1,lr}                ; store the adjustment and lr_USR
    MSR     CPSR_c, #0x1F          ; enable IRQ
    BL      C_irq_handler
    MSR     CPSR_c, #0x9F          ; disable IRQ, remain in SYS mode
    POP     {R1,lr}                ; restore stack adjustment and lr_USR
    ADD     sp, sp, R1             ; add the stack adjustment (0 or 4)
    MSR     CPSR_c, #0x92          ; switch to IRQ mode and keep IRQ
                                   ; disabled. FIQ is still enabled.
    POP     {R0-R4,R12,lr}         ; restore registers and
    MSR     SPSR_cxsf, lr          ; spsr_IRQ
    LDM     sp!, {pc}^             ; return from IRQ.

Example 38. Reentrant Interrupt for ARMv6 (non vectored interrupts)

    IMPORT C_irq_handler
    IMPORT identify_and_clear_source

    SUB         lr, lr, #4
    SRSDB       sp!,#31          ; Save LR_irq and SPSR_irq to System mode stack
    CPS         #031             ; Switch to System mode
    PUSH        {R0-R3,R12}      ; Store other AAPCS registers
    AND         R1, sp, #4
    SUB         sp, sp, R1
    PUSH        {R1, lr}
    BL          identify_and_clear_source
    CPSIE       i                ; Enable IRQ
    BL          C_irq_handler
    CPSID        i               ; Disable IRQ
    POP         {R1,lr}
    ADD         sp, sp, R1
    POP         {R0-R3, R12}     ; Restore registers
    RFEIA       sp!              ; Return using RFE from System mode stack

These examples assume that FIQ remains permanently enabled.

Show/hideSee also

Copyright © 2010-2012 ARM. All rights reserved.ARM DUI 0471G