Editor's note: In this series of ten articles Miro Samek of
Quantum Leaps details developing apps on the ARM processor using QNU,
complete with source code in C and C++.
In this part in the series, I describe the
low-level interrupt wrapper functions ARM_irq() and ARM_fiq(). These
functions have been introduced in
Part
6. In this series, and their purpose is to allow handling of
nested interrupts in the ARM architecture, which the GNU gcc
__attribute__ ((interrupt ("IRQ"))) cannot do.
Perhaps the most interesting aspect of the implementation that I
present here is its close compatibility with the new ARM v7-M
architecture (e.g., Cortex-M3) [4].
Specifically, the low-level wrapper functions deal with all the
ARM-magic internally, so that the interrupt service routines (ISRs)
that you hook to the interrupt controller can be regular C-functions.
The C-level ISRs run in the same processor mode (SYSTEM) as the code
called from main() (task-level). Also, the assembler wrapper functions
expressly avoid using the IRQ/FIQ stacks and instead nest interrupts of
all types (IRQs and FIQs) on the SYSTEM/USER stack.
The interrupt context saved to the stack is optimized for high-level
languages and just like in the ARM v7-M specification, the wrapper
functions save only the 8 registers clobbered in the ARM Architecture
Procedure Calling Standard (AAPCS) [4].
In fact, the interrupt wrapper functions generate in software the
exact same interrupt stack frame (SPSR, PC, LR, R12, R3, R2, R1, R0) as
the ARM v7-M processors generate in hardware [4].
<>I should perhaps note right away that the ARM interrupt
handling
implementation described here goes off the beaten path established by
the traditional approaches
[1,2,3,5].
Most published implementations recommend initializing the ARM vector
table to use the "auto-vectoring" feature of the interrupt controller
(see
Part 6 of this article
series). <>
Consequently the ISRs that you hook to the interrupt controller require
very special entry and exit sequences, so they cannot be regular
C-functions. Also, the interrupt handlers execute in IRQ or FIQ mode
and use the IRQ and FIQ stacks (at least for some portion of the saved
context). Consequently the stack frames as well as stack usage in the
traditional implementations are quite different compared to ARM v7-M.
The IRQ Interrupt Wrapper ARM_irq
The interrupt "wrapper" function ARM_irq() for handling the IRQ-type
interrupts is provided in the file arm_exc.s included in the code
accompanying this article series. Listing
1 below shows the entire function.
 |
| Listing
1. The ARM_irq() assembly wrapper function defined in the file
arm_exc.s. |
The highlights of the implementation are as follows:
(1) The low-level IRQ/FIQ
handlers must be written in the 32-bit instruction set (ARM), because
the ARM core automatically switches to the ARM state when IRQ/FIQ is
recognized.
(2) The ARM_irq wrapper
function is defined in the special section (.text.fastcode), which the
linker script locates in RAM (see part 3 of this article series) for
faster execution.
(3) The IRQ stack is not
used, so the banked stack pointer register r13_IRQ (sp_IRQ) is used as
a scratchpad register to temporarily hold r0 from the SYSTEM context.
NOTE: As part of the IRQ startup
sequence, the ARM processor sets the I bit in the CPSR (CPSR[7] = 1),
but leaves the F bit unchanged (typically cleared), meaning that
further IRQs are disabled, but FIQs are not. This implies that FIQ can
be recognized while the ARM core is in the IRQ mode. The FIQ handler
ARM_fiq discussed in the next section can safely preempt ARM_irq in all
places where FIQs are not explicitly disabled.
(4) Now r0 can be clobbered
with the return address from the interrupt that needs to be saved to
the SYSTEM stack.
(5) At this point the banked
lr_IRQ register can be reused to temporarily hold r1 from the SYSTEM
context.
(6) Now r1 can be clobbered
with the value of spsr_IRQ register (Saved Program Status Register)
that needs to be saved to the SYSTEM stack.
(7) Mode is changed to
SYSTEM with IRQ interrupt disabled, but FIQ explicitly enabled. This
mode switch is performed to get access to the SYSTEM registers.
NOTE: The F bit in the CPSR is
intentionally cleared at this step (meaning that the FIQ is explicitly
enabled). Among others, this represents the workaround for the Problem
2 described in ARM Technical Note "What happens if an interrupt occurs
as it is being disabled?" (see the previous Part
7 of this article series).
(8) The SPSR register and
the return address from the interrupt (PC after the interrupt) are
pushed on the SYSTEM stack.
(9) All registers (except
r0 and r1) clobbered by the AAPCS (ARM Architecture Procedure Call
Standard) [4] are pushed on the SYSTEM stack.
(10) The SYSTEM stack
pointer is placed in r0 to be visible in the IRQ mode.
(11) The SYSTEM stack
pointer is adjusted to make room for two more registers of the saved
IRQ context. By adjusting the SYSTEM stack pointer, the IRQ handler can
still keep FIQ enabled without the concern of corrupting the SYSTEM
stack space reserved for the IRQ context.
(12) The mode is switched
back to IRQ with IRQ interrupt disabled, but FIQ still enabled. This is
done to get access to the rest of the context sitting in the IRQ-banked
registers.
(13) The context is entirely
saved by pushing the original r0 and r1 (still sitting in the banked
IRQ Registers r14_IRQ and r13_IRQ, respectively) to the SYSTEM stack.
At this point the saved SYSTEM stack frame contains 8 registers and
looks as follows (this is exactly the ARM v7-M interrupt stack frame
[4]):

(14) The mode is switched
once more to SYSTEM with IRQ disabled and FIQ enabled. Please note that
the stack pointer sp_SYS points to the top of the stack frame, because
it has been adjusted after the first switch to the SYSTEM mode at point
(11).
(15-17) The board-specific
function BSP_irq() is called to perform the interrupt processing at the
application-level. Please note that BSP_irq() is now a regular C
function in ARM or Thumb. Typically, this function uses the
silicon-vendor specific interrupt controller (such as the Atmel AIC) to
vector into the current interrupt, as was discussed in part 6 of this
article.
NOTE: The BSP_irq() function is
entered with IRQ disabled (and FIQ enabled), but it can internally
unlock IRQs, if the MCU is equipped with an interrupt controller that
performs prioritization of IRQs in hardware.
(18) All interrupts (IRQ and
FIQ) are locked to execute the following instructions atomically.
(19) The sp_SYS register is
moved to r0 to make it visible in the IRQ mode.
(20) Before leaving the
SYSTEM mode, the sp_SYS stack pointer is adjusted to un-stack the whole
interrupt stack frame of 8 registers. This brings the SYSTEM stack to
exactly the same state as before the interrupt occurred.
NOTE: Even though the SYSTEM stack
pointer is moved up, the stack contents have not been restored yet. At
this point it's critical that the interrupts are completely locked, so
that the stack contents above the adjusted stack pointer cannot be
corrupted.
(21) The mode is changed to
IRQ with IRQ and FIQ interrupts locked to perform the final return from
the IRQ.
(22) The SYSTEM stack
pointer is copied to the banked sp_IRQ, which thus is set to point to
the top of the SYSTEM stack
(23-24) The value of SPSR is
loaded from the stack (please note that the SPSR is now 7 registers
away from the top of the stack) and placed in SPSR_irq.
(25) The 6 registers are
popped from the SYSTEM stack. Please note the special version of the
LDM instruction (with the '^' at the end), which means that the
registers are popped from the SYSTEM/USER stack. Please also note that
the special LDM(2) instruction does not allow the write-back, so the
stack pointer is not adjusted. (For more information please refer to
Section "LDM(2)" in the "ARM Architecture Reference Manual" [5].)
Listing 4(26) It's
important not to access any banked register after the special LDM(2)
instruction.
Listing 4(27) The return
address is retrieved from the stack. Please note that the return
address is now 6 registers away from the top of the stack.
Listing 4(28) The interrupt
return involves loading the PC with the return address and the CPSR
with the SPSR, which is accomplished by the special version of the MOVS
pc,lr instruction.
The FIQ Interrupt Wrapper ARM_fiq
The interrupt "wrapper" function ARM_fiq() for handling the FIQ-type
interrupts is provided in the file arm_exc.s included in the code
accompanying this article series. Listing
2 below shows the entire function.
 |
| Listing
2. The ARM_fiq() assembly wrapper function defined in the file
arm_exc.s. |
The ARM_fiq() "wrapper" function is very similar to the IRQ handler (Listing 1), except the FIQ mode is
used instead of the IRQ mode. The following comments explain only the
slight, but important differences in disabling interrupts and the
responsibilities of the C-level handler BSP_fiq().
(1) The FIQ handler is
always entered with both IRQ and FIQ disabled, so the FIQ mode is not
visible in any other modes. The ARM_fiq handler keeps the IRQ and FIQ
locked at all times.
(2) The mode is switched to
SYSTEM to get access to the SYSTEM stack pointer. Please note that both
IRQ and FIQ interrupts are kept disabled throughout the FIQ handler.
(3) The C-function BSP_fiq()
is called to perform the interrupt processing at the application-level.
Please note that BSP_fiq() is now a regular C function in ARM or THUMB.
Unlike the IRQ, the FIQ interrupt is often not covered by the priority
controller, therefore the BSP_fiq() should NOT unlock interrupts.
NOTE: The BSP_fiq() function is
entered with both IRQ and FIQ interrupts disabled and it should NEVER
enable any interrupts. Typically, the FIQ line to the ARM core does not
have a priority controller, even though the FIQ line typically goes
through a hardware interrupt controller.
Coming Up Next
In the next Part 9 in this series,
I'll wrap up the interrupt handling for ARM by presenting examples of
the interrupt service routines (ISRs) in C, as well as the
initialization of the vector table and the interrupt controller. I'll
also discuss a rudimentary policy of handling other ARM Exceptions,
such as Undefined Instruction or Data Abort.
To read Part 1, go to What's
need to get started.
To read Part 2, go to
Startup code and the low level initialization
To read Part 3, go to The
Linker Script.
To read Part 4, go to C
and C++ compiler options
To read Part 5, go to Fine-tuning
the application
To read Part 6, go to General
description of interrupt handling
To read Part 7, go to Interrupt
Locking and Unlocking
To download the C and C++ source code associated with this
article series, go to Embedded.com's Downloadable Code page,
or go to Blinky for C and Blinky for C++ to download the
Zip files.
Miro
Samek, Ph.D., is president of Quantum
Leaps, LLC. He can be
contacted at miro@quantum-leaps.com.
References
[1] ARM Technical Support Note
"Writing Interrupt
Handlers" available online.
[2] Philips Application Note
AN10381 "Nesting
of Interrupts in the LPC2000" available online.
[3] Atmel Application Note
"Interrupt
Management: Auto-vectoring and Prioritization" available
online.
[4] ARM Limited, "ARM
v7-M
Architecture Application Level Reference Manual", available on
line. .
[5] Seal, David Editor, "ARM
Architecture Manual, 2nd Edition", Addison Wesley 2000.