9.5.2. Reentrant interrupt handlers

Note

The following method works for both IRQ and FIQ interrupts. However, because FIQ interrupts are meant to be serviced as quickly as possible there will normally be only one interrupt source, so it may not be necessary to allow for reentrancy.

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 store all the state information required for reentrant interrupt handlers, so you must write your top level interrupt handler in assembly language.

A reentrant interrupt handler must save the necessary IRQ state, switch processor modes, and save the state for the new processor mode before branching to a nested subroutine or C function.

In ARM architecture 4 or later you can switch to System mode. System mode uses the User mode registers, and allows privileged access that may be required by your exception handler. Refer to System mode for more information. In ARM architectures prior to architecture 4 you must switch to Supervisor mode instead.

The steps needed to safely re-enable interrupts in an IRQ handler are:

  1. Construct return address and save 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 9.13 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 9.13. 

	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
Copyright © 1997, 1998 ARM Limited. All rights reserved.ARM DUI 0040D
Non-Confidential