6.7.3. Reentrant interrupt handlers

If an interrupt handler re-enables interrupts, then calls a subroutine, and another interrupt occurs, the return address of the subroutine (stored in lr_IRQ) is corrupted when the second IRQ is taken. 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.

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.

In ARMv4 or later you can switch to System mode. System mode uses the User mode registers, and enables privileged access that might be required by your exception handler. See System mode for more information. In ARM architectures prior to ARMv4 you must switch to Supervisor mode instead.

Note

This method works for both IRQ and FIQ interrupts. However, because FIQ interrupts are meant to be serviced 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 safely re-enable interrupts in an IRQ handler are:

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

  2. Save the work registers and spsr_IRQ.

  3. Clear the source of the interrupt.

  4. Switch to System mode and re-enable interrupts.

  5. Save User mode link register and non callee-saved registers.

  6. Call the C interrupt handler function.

  7. When the C interrupt handler returns, restore User mode registers and disable interrupts.

  8. Switch to IRQ mode, disabling interrupts.

  9. Restore work registers and spsr_IRQ.

  10. Return from the IRQ.

Example 6.15 shows how this works for System mode. Registers r12 and r14 are used as temporary work registers after lr_IRQ is pushed on the stack.

Example 6.15. 

    PRESERVE8

    AREA INTERRUPT, CODE, READONLY
    IMPORT C_irq_handler
IRQ
    SUB     lr, lr, #4        ; construct the return address
    STMFD   sp!, {lr}         ; and push the adjusted lr_IRQ
    MRS     r14, SPSR         ; copy spsr_IRQ to r14
    STMFD   sp!, {r12, r14}   ; save work regs and spsr_IRQ

    ; Add instructions to clear the interrupt here
    ; then re-enable interrupts.


    MSR     CPSR_c, #0x1F     ; switch to SYS mode, FIQ and IRQ
                              ; enabled. USR mode registers
                              ; are now current.
    STMFD  sp!, {r0-r3, lr}   ; save lr_USR and non-callee 
                              ; saved registers
    BL      C_irq_handler     ; branch to C IRQ handler.
    LDMFD   sp!, {r0-r3, lr}  ; restore registers
    MSR     CPSR_c, #0x92     ; switch to IRQ mode and disable
                              ; IRQs. FIQ is still enabled.

    LDMFD   sp!, {r12, r14}   ; restore work regs and spsr_IRQ
    MSR     SPSR_cf, r14
    LDMFD   sp!, {pc}^        ; return from IRQ.
    END

This example assumes that FIQ remains permanently enabled.

Copyright © 2002-2006 ARM Limited. All rights reserved.ARM DUI 0203G
Non-Confidential