6.2.7. Interrupt handlers
This section describes how to write interrupt handlers.
Levels of external interrupt
The ARM processor has two levels of external interrupt, FIQand IRQ, both of which are level-sensitive active LOW signals intothe processor. For an interrupt to be taken, the appropriate disablebit in the CPSR must be clear.
FIQs have higher priority than IRQs in the following ways:
-
FIQs arehandled first when multiple interrupts occur.
-
Handling an FIQ causes IRQs and subsequent FIQsto be disabled, preventing them from being handled until after theFIQ handler enables them. This is usually done by restoring theCPSR from the SPSR at the end of the handler.
The FIQ vector is the last entry in the vector table so thatthe FIQ handler can be placed directly at the vector location andrun sequentially from that address. This removes the requirementfor a branch and its associated delay, and also means that if thesystem has a cache, the vector table and FIQ handler might all belocked down in one block within it. This is important because FIQsare designed to handle interrupts as quickly as possible. The fiveextra FIQ mode banked registers enable status to be held between callsto the handler, again increasing execution speed.
Note
An interrupt handler must contain code to clear the sourceof the interrupt.
Reentrant interrupt handlers
If an interrupt handler enables interrupts before callinga subroutine and another interrupt occurs, the return address ofthe subroutine stored in the IRQ mode LR is corrupted when the secondIRQ is taken. This is because the processor automatically savesthe return address into the IRQ mode LR
for thenew interrupt overwriting the return address for the subroutine.This results in an infinite loop when the subroutine in the originalinterrupt tries to return.
A reentrant interrupt handler must save the IRQ state, switchprocessor modes, and save the state for the new processor mode beforebranching to a nested subroutine or C function. It must also ensurethat the stack is eight-byte aligned for the new processor modebefore calling AAPCS-compliant compiled C code that might use LDRD
or STRD
instructionsor eight-byte aligned stack-allocated data. There is more informationabout stack alignment issues in the ABI for the ARM ArchitectureAdvisory Note 1- SP must be 8-byte aligned on entry to AAPCS-conformingfunctions (ARM IHI 0046A).
Using the __irq
keyword in C does not causethe SPSR to be saved and restored, as required by reentrant interrupthandlers, so you must write your top level interrupt handler inassembly language.
In ARMv4 or later you can switch to System mode if you require privilegedaccess. See System mode formore information.
Note
This method works for both IRQ and FIQ interrupts. However,because FIQ interrupts are meant to be handled as quickly as possiblethere is normally only one interrupt source, so it might not benecessary to provide for reentrancy.
The steps required to enable interrupts safely in an IRQ handlerare:
-
Construct thereturn address and save it on the IRQ stack.
-
Save the work registers, non callee-saved registersand IRQ mode SPSR.
-
Clear the source of the interrupt.
-
Switch to System mode, keeping IRQs disabled.
-
Check that the stack is eight-byte aligned and adjustif necessary.
-
Save the User mode LR and the adjustment, 0 or 4for Architectures v4 or v5TE, used on the User mode SP.
-
Enable interrupts and call the C interrupt handlerfunction.
-
When the C interrupt handler returns, disable interrupts.
-
Restore the User mode LR and the stack adjustmentvalue.
-
Readjust the stack if necessary.
-
Switch to IRQ mode.
-
Restore other registers and IRQ mode SPSR.
-
Return from the IRQ.
Example 6.2 and Example 6.3 shows howthis works for System mode.
Example 6.2. Reentrant interrupt handler forARMv4/v5TE
PRESERVE8 AREA INTERRUPT, CODE, READONLY IMPORT C_irq_handler
IMPORT identify_and_clear_source IRQ_Handler 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. END
Example 6.3. Reentrant Interrupt for ARMv6 (non vectored interrupts)
PRESERVE8 AREA INTERRUPT, CODE, READONLY IMPORT C_irq_handler IMPORT identify_and_clear_source IRQ_Handler 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 END
These examples assume that FIQ remains permanently enabled.
Example interrupt handlers in assemblylanguage
Interrupt handlers are often written in assembly languageto ensure that they execute quickly. The following sections givesome examples:
Single-channel DMA transfer
Example 6.4 shows aninterrupt handler that performs interrupt driven I/O to memory transfers,soft DMA. The code is an FIQ handler. It uses the banked FIQ registersto maintain state between interrupts. This code is best situatedat location 0x1C
.
In the example code:
-
R8
-
Pointsto the base address of the I/O device that data is read from.
IOData
-
Is the offset fromthe base address to the 32-bit data register that is read. Readingthis register clears the interrupt.
R9
-
Points to the memorylocation to where that data is being transferred.
R10
-
Points to the lastaddress to transfer to.
The entire sequence for handling a normal transfer is fourinstructions. Code situated after the conditional return is usedto signal that the transfer is complete.
Example 6.4. FIQ handler
LDR R11, [R8, #IOData] ; Load port data from the IO device. STR R11, [R9], #4 ; Store it to memory: update the pointer. CMP R9, R10 ; Reached the end ? SUBLSS pc, lr, #4 ; No, so return. ; Insert transfer complete ; code here.
Byte transfers can be made by replacing the load instructionswith load byte instructions. Transfers from memory to an I/O deviceare made by swapping the addressing modes between the load instructionand the store instruction.
Dual-channel DMA transfer
Example 6.5 issimilar to Example 6.4, exceptthat there are two channels being handled. The code is an FIQ handler.It uses the banked FIQ registers to maintain state between interrupts.It is best situated at location 0x1C
.
In the example code:
-
R8
-
Points to the base address of the I/O device fromwhich data is read.
IOStat
-
Is the offset fromthe base address to a register indicating which of two ports causedthe interrupt.
IOPort1Active
-
Is a bitmask indicating if the first port caused the interrupt. Otherwiseit is assumed that the second port caused the interrupt.
IOPort1,
IOPort2
-
Areoffsets to the two data registers to be read. Reading a data registerclears the interrupt for the corresponding port.
R9
-
Points to the memorylocation to which data from the first port is being transferred.
R10
-
Points to the memorylocation to which data from the second port is being transferred.
R11,
R12
-
Pointto the last address to transfer to. This is
R11
forthe first port,R12
for the second.
The entire sequence to handle a normal transfer takes nineinstructions. Code situated after the conditional return is usedto signal that the transfer is complete.
Example 6.5. FIQ handler
LDR sp, [R8, #IOStat] ; Load status register to find which port ; caused the interrupt. TST sp, #IOPort1Active LDREQ sp, [R8, #IOPort1] ; Load port 1 data. LDRNE sp, [R8, #IOPort2] ; Load port 2 data. STREQ sp, [R9], #4 ; Store to buffer 1. STRNE sp, [R10], #4 ; Store to buffer 2. CMP R9, R11 ; Reached the end? CMPLE R10, R12 ; On either channel? SUBSNE pc, lr, #4 ; Return ; Insert transfer complete code here.
Byte transfers can be made by replacing the load instructionswith load byte instructions. Transfers from memory to an I/O deviceare made by swapping the addressing modes between the conditionalload instructions and the conditional store instructions.
Interrupt prioritization
Example 6.6 dispatchesup to 32 interrupt sources to their appropriate handlers. Becauseit is designed for use with the normal interrupt vector, IRQ, itis branched to from location 0x18
.
External Vectored Interrupt Controller (VIC) hardwareis used to prioritize the interrupt and present the high-priorityactive interrupt in an I/O register.
In the example code:
-
IntBase
-
Holdsthe base address of the interrupt controller.
IntLevel
-
Holds the offsetof the register containing the highest-priority active interrupt.
R13
-
Is assumed to pointto a small full descending stack.
Interrupts are enabled after ten instructions, including thebranch to this code.
The specific handler for each interrupt is entered, with allregisters preserved on the stack, after two more instructions.
In addition, the last three instructions of each handler areexecuted with interrupts turned off again, so that the SPSR
canbe safely recovered from the stack.
Note
Application Note 30: Software Prioritization ofInterrupts describes multiple-source prioritizationof interrupts using software, as opposed to using the VIC hardwareas described here.
Example 6.6. Dispatching interruptsto handlers
; first save the critical state SUB lr, lr, #4 ; Adjust the return address ; before we save it. STMDB sp!, {lr} ; Stack return address MRS lr, SPSR ; get the SPSR ... PUSH {R12,lr} ; ... and stack that plus a ; working register too. ; Now get the priority level of the ; highest priority active interrupt. MOV R12, #IntBase ; Get the interrupt controller's ; base address. LDR R12, [R12, #IntLevel] ; Get the interrupt level (0 to 31). ; Now read-modify-write the CPSR
; to enable interrupts. MRS lr, APSR ; Read the status register. BIC lr, lr, #0x80 ; Clear the I bit ; (use 0x40 for the F bit). MSR CPSR_c, lr ; Write it back to re-enable ; interrupts and LDR pc, [pc, R12, LSL #2] ; jump to the correct handler. ; PC base address points to this ; instruction + 8 NOP ; pad so the PC indexes this table. ; Table of handler start addresses DCD Priority0Handler DCD Priority1Handler DCD Priority2Handler ; ... Priority0Handler PUSH {R0-R11} ; Save other working registers. ; Insert handler code here. ; ... POP {R0-R11} ; Restore working registers (not R12). ; Now read-modify-write the CPSR
; to disable interrupts. MRS R12, APSR ; Read the status register. ORR R12, R12, #0x80 ; Set the I bit ; (use 0x40 for the F bit). MSR CPSR_c, R12 ; Write it back to disable interrupts. ; Now that interrupt disabled, can safely
; restore SPSR then return. POP {r12,lr} ; Restore R12 and get SPSR. MSR SPSR_cxsf, lr ; Restore status register from R14. LDM sp!, {pc}^ ; Return from handler. Priority1Handler ; ...
Context switch
Example 6.7 performsa context switch on the User mode process. The code is based arounda list of pointers to Process Control Blocks (PCBs)of processes that are ready to run.
Figure 6.2 showsthe layout of the PCBs that the example expects.
Figure 6.2. PCB layout
The pointer to the PCB of the next process to run is pointedto by R12
, and the end of the list has a zeropointer. Register R13
is a pointer to the PCB,and is preserved between time slices, so that on entry it pointsto the PCB of the currently running process.
Example 6.7. Context switchon the User mode process
STM sp,{R0-lr}^ ; Dump user registers above R13. MRS R0, SPSR ; Pick up the user status STMDB sp, {R0, lr} ; and dump with return address below. LDR sp, [R12], #4 ; Load next process info pointer. CMP sp, #0 ; If it is zero, it is invalid LDMDBNE sp, {R0, lr} ; Pick up status and return address. MSRNE SPSR_cxsf, R0 ; Restore the status. LDMNE sp, {R0 - lr}^ ; Get the rest of the registers NOP SUBSNE pc, lr, #4 ; and return and restore CPSR. ; Insert "no next process code" here.