Building Bare-Metal ARM Systems with GNU: Part 6

Editor'snote: In this series of ten articles Miro Samek of Quantum Leapsdetails developing apps on the ARM processor using QNU, complete withsource code in C and C++.

In this part of the series I tackle interrupt handlingfor the ARM processor in the simple foreground/background architecturewithout any underlying multitasking OS or kernel (bare metal). Theinterrupt handling scheme presented here fully supports nesting ofinterrupts and can work with or without an interrupt controllerexternal to the ARM7/ARM9 core.

In this part I describe interrupt handling in general terms and inthe following installments I provide detailed description of interruptlocking policy, interrupt handler “wrappers” in assembly, C-levelinterrupt service routines, and finally interrupt testing strategiesfor ARM-based MCUs.

The recommended reading for this part includes: ARM TechnicalSupport Note “Writing Interrupt Handlers” [1] , Philips Application NoteAN10381 “Nesting of Interrupts in the LPC2000” [2] , and Atmel Application Note”Interrupt Management: Auto-vectoring and Prioritization” [3] .

Problem Description
The ARM core supports two types of interrupts: Interrupt Request (IRQ)and Fast Interrupt Request (FIQ), as well as several exceptions:Undefined Instruction, Prefetch Abort, Data Abort, and SoftwareInterrupt. Upon encountering an interrupt or an exception the ARM coredoes not automatically push any registers to the stack.

If the application wants to nest interrupts (to take advantage ofthe prioritized interrupt controller available in most ARM-based MCUs),the responsibility is entirely with the application programmer to saveand restore the ARM registers.

GNU gcc provides the function __attribute__ ((interrupt (“IRQ”))) toindicate that the specified C/C++ function is an IRQ handler (similarlythe __attribute__ ((interrupt (“FIQ”))) is provided for FIQ handlers).

However, these attributes are only designed for “simple”(non-nesting) interrupt handlers. This is because functions designatedas interrupts do not store all of the context information (e.g., theSPSR is not saved), which is necessary for fully re-entrant interrupts [1] .

At the same time, most ARM-based MCUs contain a prioritizedinterrupt controller that specifically supports nesting andprioritization of multiple interrupt sources. This powerful hardwarefeature cannot be used, however, unless the software is actuallycapable of handling nested interrupts.

Interrupt Handling Strategy
To enable interrupt nesting, the handler must at some point unlockinterrupts, which are automatically locked at the ARM core level uponthe IRQ/FIQ entry. Generally, all documented strategies for handlingnested interrupts in the ARM architecture involve switching the modeaway from IRQ (or FIQ) to the mode used by the task-level code beforeenabling interrupts [1, 2, 3] .

The standard techniques also use multiple stacks during interrupthandling. The IRQ/FIQ mode stack is used for saving a part of theinterrupt context and the SYSTEM/USER stack (or sometimes the SVCstack) is used for saving the rest of the context. ARM Ltd. recommendsusing SYSTEM mode while programming reentrant interrupt handlers [1] .

The interrupt handling strategy for bare-metal ARM system describedhere also switches away from the IRQ/FIQ mode to SYSTEM mode beforeenabling interrupt nesting, but differs from the other schemes in thatall the CPU context is saved to the SYSTEM/USER stack and the IRQ/FIQstacks are not used at all.

Saving the context to the separate interrupt stack has value only inmultitasking kernels that employ a separate stack for each task. Usingmultiple stacks in the simple foreground/background architecture withonly one background task (the main() loop) has no value and only addscomplexity.

Figure1. General interrupt handling strategy with the interrupt controller

Figure 1 above illustratesthe steps of IRQ processing. The sequence starts when the ARM corerecognizes the IRQ. The ARM core switches to the IRQ mode(CPSR[0-4]==0x12) and the PC is forced to 0x18.

As described in Part 2 of thisseries (“Startup Code and the Low-level Initialization”),thehardware vector table at address 0x18 is initialized to the instructionLDR pc,[pc,#0x18].

This instruction loads the PC with the secondary jump table entry at0x38, which must be initialized to the address of the ARM_irq handler”wrapper” function written in assembly. The upcoming Part 8 of thisseries of articles will describe the ARM_irq “wrapper” function indetail.

For now, in this general overview I'll ignore the implementationdetails of ARM_irq and simply summarize that it saves all requiredregisters and switches the mode away from IRQ to the SYSTEM mode(CPSR[0-4]==0x1F). The ARM_irq assembler “wrapper” function is genericand you don't need to adapt it in any way for various ARM MCUs.

As shown in the middle section of Figure1 above , the generic ARM_irq “wrapper” function then calls theinterrupt handler BSP_irq(), which is board-specific because it dependson the particular interrupt controller (or the lack of it).

BSP_irq() can be coded in C as a regular C-function (not aninterrupt (“IRQ”) function!) and is called in the SYSTEM mode, justlike all other C functions in the application. Please note, though,that BSP_irq() is invoked with the IRQ disabled and FIQ interruptenabled, which is the same state of the interrupt bits as the settingestablished in hardware upon the IRQ entry.

In the absence of an interrupt controller, you can handle the IRQinterrupt directly in BSP_irq(). Generally, in this case you should notre-enable IRQ interrupt throughout the IRQ processing because you haveno hardware mechanism to prevent a second instance of the sameinterrupt from preempting the current one.

The interrupt locking policy that I describe in the next part ofthis article series is safe to use in the IRQ C-level handlers withoutan interrupt controller.

In the presence of an interrupt controller, the sequence is a bitmore involved (see again Figure 1). The function BSP_irq() first readsthe current interrupt vector from the interrupt controller.

The read cycle of the vector address starts prioritization of thisinterrupt level in the interrupt controller, so after this instructionit's safe to re-enable all interrupts (e.g., via the assemblyinstruction asm(“MSR cpsr_c,#0x1F”);).

Next, the BSP_irq() function calls the interrupt vector via thepointer to function syntax ((*vect)()). For this to work, the interruptcontroller must be initialized with the addresses of the interruptservice routines (ISRs), as shown in the bottom part of Figure 1 above .

After the interrupt handler returns, the BSP_irq() function locksboth IRQ and FIQ interrupts (e.g., via the assembly instructionasm(“MSR cpsr_c,#(0x1F | 0x80 | 0x40)”);). Finally, BSP_irq() writesthe End-Of-Interrupt instruction to the interrupt controller, whichterminates the prioritization of this interrupt level.

The code accompanying this article provides the example of theBSP_irq() function for the Atmel Advanced Interrupt Controller (AIC).Other interrupt controllers use slightly different register names andaddresses, but work very similarly.

NOTE: The function BSP_irq() mustbe compiled to ARM, if you use the inline assembly instruction asm(“MSRcpsr_c,#0x1F”) to unlock and instruction asm(“MSR cpsr_c,#(0x1F |0x80)”) to loc k interrupts.The MSR instruction is not available in the Thumb instruction set.

The BSP_irq() function returns eventually to the generic ARM_irq()assembler wrapper function (see the middle section of Figure 1). TheARM_irq() assembler “wrapper” restores the context from the SYSTEMstack, performs the mode switch back to IRQ and performs the standardreturn from exception via the MOVS pc,lr instruction.

FIQ Handling
Handling of FIQ interrupts is similar to IRQ as far as the assembler”wrapper” function and the vector table initialization are concerned.The main difference between FIQ and IRQ is that the FIQ line istypically not managed by the priority controller (such as the AtmelAIC, NXP VIC, or others), as illustrated in Figure 2 below.

Figure2 Typical ARM system with an interrupt controller external to the ARMcore

The consequences of this hardware design are at least two-fold.First, you can simply handle the FIQ directly in the BSP_fiq() C-levelfunction, without any indirection via the interrupt controller.

In other words, even though most interrupt controllers insidepopular ARM MCUs support the vectoring feature for FIQ, it does not addmuch value. The second, and far more important consequence, is that youshould never enable FIQ or IRQ interrupts throughout the FIQprocessing.

If you were to enable FIQ or IRQ, you would make the currentlyexecuting FIQ handler vulnerable to preemptions by the IRQs or thesecond instance of the currently handled FIQ. Both cases representpriority inversions.

The interrupt locking policy that I describe in the next part ofthis article series (Part 7) is safe to use in the FIQ C-level handlerfunction. The accompanying code for this article includes the exampleof the FIQ coded directly in BSP_fiq() handler function.

No Auto-Vectoring
It's perhaps important to note that the interrupt handling strategypresented here does not use the auto-vectoring feature described in theapplication notes [2} and [3] . Auto-vectoring occurs when thefollowingLDR instruction is located at the address 0x18 for the IRQ (thisexample pertains to the Atmel's AIC):

    ORG 0x18
    LDR pc,[pc,#-0xF20]

When an IRQ occurs, the ARM core forces the PC to address 0x18 andexecutes the LDR pc,[pc,#-0xF20] instruction. When the instruction ataddress 0x18 is executed, the effective address is: 0x20 ” 0xF20 =0xFFFFF100 (0x20 is the value of the PC when the instruction at address0x18 is executed due to pipelining of the ARM core).

This causes the ARM core to load the PC with the value read from theAIC_IVR register located at 0xFFFFF100. The read cycle causes theAIC_IVR register to return the address of the currently activeinterrupt service routine.

Thus, the single LDR pc,[pc,#-0xF20] instruction has the effect ofstarting the prioritization of the current IRQ and directly jumping tothe correct ISR, which is called auto-vectoring.

The consequence of auto-vectoring is that the interrupt serviceroutines hooked to the interrupt controller cannot be plain C-functionsbut rather each one of them must deal directly with the complexities ofthe IRQ or FIQ modes.

Instead of repeating the IRQ entry and exit sequence in each andevery IRQ interrupt service routine, the implementation I describe hereuses only one generic, low-level, re-entrant IRQ handler (ARM_irq) thatencapsulates the “ARM-magic” and then calls the higher-level handlerBSP_irq(), which can be a plain C function. Similar approach is takenfor handling FIQs.

Please note that even though “auto-vectoring” is not used, theBSP_irq() function can take full advantage of the vectoring feature ofthe interrupt controller by reading the vector from the interruptcontroller and calling the handler via a pointer-to-function. This,however, happens later in the IRQ sequence and strictly speaking cannotbe called “auto-vectoring”.

Coming Up Next
In Part 7 next in this series of articles I'll describe the interruptlocking policy for ARM that would be safe for both IRQ and FIQinterrupts as well as the task level code (the code called frommain()). I'll explain the details of the low-level interrupt handlersin Part 8. Stay tuned.

To read Part 1, go to What'sneed to get started.
To read Part 2, go to Startup code and the low level initialization
To read Part 3, go to TheLinker Script.
To read Part 4, go to  Cand C++ compiler options
To read Part 5, go to  Fine-tuningthe application

To download the C and C++ sourcecode 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 theZipfiles.

Miro Samek, Ph.D., is president of Quantum Leaps, LLC. He can becontacted at miro@quantum-leaps.com.

References
[1] ARM Technical Support Note “Writing InterruptHandlers” available online.
[2] Philips Application NoteAN10381 “Nestingof Interrupts in the LPC2000” available online as a PDF file.
[3] Atmel Application Note “InterruptManagement: Auto-vectoring and Prioritization” available online asa PDF file. 

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.