Building Bare-Metal ARM Systems with GNU: Part 8
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) . 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) .
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 .<>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 = 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)  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 ):
(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" .)
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
 ARM Technical Support Note "Writing Interrupt Handlers" available online.
 Philips Application Note AN10381 "Nesting of Interrupts in the LPC2000" available online.
 Atmel Application Note "Interrupt Management: Auto-vectoring and Prioritization" available online.
 ARM Limited, "ARM v7-M Architecture Application Level Reference Manual", available on line. .
 Seal, David Editor, "ARM Architecture Manual, 2nd Edition", Addison Wesley 2000.