5.12 Reentrant interrupt handlers

You must follow some steps to enable interrupts safely in an IRQ handler.

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.


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.

To enable interrupts safely in an IRQ handler:

  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. These examples assume that FIQ remains permanently enabled.

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.

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
Non-ConfidentialPDF file icon PDF versionARM DUI0471M
Copyright © 2010-2016 ARM Limited or its affiliates. All rights reserved.