Building Bare-Metal ARM Systems with GNU: Part 7

Miro Samek, Quantum Leaps

August 05, 2007

Miro Samek, Quantum LeapsAugust 05, 2007

Editor's note: In this series of ten articles Miro Samek of Quantum Leaps details developing applications with C and C++ on the ARM processor using the open source QNU development tools.

In this part of this article series I describe the interrupt locking and unlocking policy for ARM that will be safe for both IRQ and FIQ interrupt handlers as well as the task level code (the code called from main()).

You would probably never think that interrupt locking could deserve the whole article. But the ARM architecture somehow manages to make it amazingly complex. The recommended reading for this part includes: ARM Technical Support Note "What happens if an interrupt occurs as it is being disabled?" [1], and Atmel Application Note "Disabling Interrupts at Processor Level" [2].

Problem Description
In the simple foreground/background architecture without any underlying multitasking OS or kernel (bare metal) the foreground (ISRs) communicates with the background (the main() loop) by means of shared variables.

The background code has the responsibility of protecting these shared variables from corruption by the asynchronously firing ISRs. The only mutual exclusion mechanism available in this simple architecture is to briefly lock interrupts before accessing the shared resource and to unlock the interrupts after releasing the resource.

The section of code executing atomically between locking and unlocking interrupts is often called the critical section or critical region. Of course, you should keep the time spend inside each critical section to the minimum, so that you don't extend the interrupt latency of the system.

Critical sections are necessary not just in the task-level code callable from the main() loop. If nesting of IRQ interrupts is allowed (which I assume in this article) critical sections are necessary also inside the IRQ interrupts.

Finally, the same critical sections used in the IRQs handlers might be also used inside the FIQ handler, simply because of the coding convenience. Even though the FIQ handler runs with interrupts locked at all times, so it does not really need to use a critical section (See Part 6 of this series), experience shows that programmers can all too easily forget that FIQ requires a completely different interrupt locking policy than all other interrupt handlers.

A problem would arise if interrupts were inadvertently unlocked inside the FIQ handler upon the exit from a critical section. Please note that a critical section can be buried inside a deeply nested function call chain.

In summary, a real-life, bare-metal ARM project requires a universal interrupt locking and unlocking policy that would be safe to use from the task-level, nested IRQ handlers, and the FIQ handler.

The Policy of Saving and Restoring Interrupt Status
The interrupt locking policy that has all the properties required in this case is the policy of saving and restoring the interrupt status. This policy is common, and is used for example inside the VxWorks RTOS (see the function pair intLock()/intUnlock() documented at Stanford SLAC's Web site.   The following code snippet shows how you use this type of critical section:

First, you need to declare a temporary variable (typically a stack or register variable) that will hold the interrupt status throughout the duration of the critical section [1].

For portability, the type of the interrupt status is declared as a macro ARM_INT_KEY_TYPE, which along with the macros for locking and unlocking interrupts, is defined in the file arm_exc.h included in the code accompanying this article.

Next, you lock interrupts by means of another macro ARM_INT_LOCK(), which saves the interrupt status in the int_lock_key variable [2]. Finally, you unlock the interrupts by means of the macro ARM_INT_UNLOCK(), which restores the interrupt lock to the state before the matching ARM_INT_LOCK() has been called [3].

This policy allows nesting critical sections, because the interrupt state is preserved across the critical section in a temporary variable. In other words, upon the exit from a critical section the interrupts are actually unlocked in the ARM_INT_UNLOCK() macro only if they were unlocked before the invocation of the matching ARM_INT_LOCK() macro. Conversely, interrupts will remain locked after the ARM_INT_UNLOCK() macro if they were locked before the matching ARM_INT_LOCK() macro.

Critical Section Implementation with GNU gcc
The macros ARM_INT_KEY_TYPE, ARM_INT_LOCK(), and ARM_INT_UNLOCK() are defined in the arm_exc.h header file provided in the code accompanying this article and shown in Listing 1 below.

Listing 1 Implementation of the critical for ARM with GNU gcc

The main points of the implementation are as follows:

(1) The ARM_INT_KEY_TYPE macro represents the type of the interrupt lock status preserved across the critical section. In the case of the ARM processor, the interrupt lock key is the value of the CPSR register (an int is 32-bit wide in ARM gcc).

(2) GNU gcc for ARM pre-defines the macro __thumb__ when it is invoked with the -mthumb compiler option to compile the code in the 16-bit Thumb mode.

(3) The Thumb instruction set does not include the MSR/MRS instructions, which are the only way to manipulate the interrupt bits in the CPSR register.

Therefore the ARM_INT_LOCK() macro invokes an ARM function ARM_int_lock_SYS(), which locks the interrupts and returns the value of the CPSR before locking both the IRQ and FIQ in the CPSR. The gcc compiler and linker add the appropriate Thumb-to-ARM and ARM-to-Thumb call "veneers" automatically thanks to the "mthumb-interwork compiler option (See Part 4 of this article series).

(4) The ARM_INT_UNLOCK() macro is defined as a call to the ARM function ARM_int_unlock_SYS(), which restores the CPSR from the interrupt lock key argument. Both ARM_int_lock_SYS() and ARM_int_unlock_SYS() are defined in the assembly module arm_exc.s. The assembly code used inside these two functions is actually identical as the inline assembly used when the code is compiled to ARM (see Highlights for Listing 1(9-11)).

(5-6) The C-prototypes of the assembly functions must be provided for the C compiler. (NOTE: for the C++ version, the prototypes must be defined with the extern "C" linkage specification.)

(7) If the ARM instruction set is used (the GNU gcc is invoked with the compiler option "marm), the critical section can be defined more optimally without the overhead of a function call for each entry and exit from a critical section.

(8,11) The macro ARM_INT_LOCK() is defined as the do {...} while (0) compound statement to guarantee syntactically-correct parsing of the macro in every context (including the dangling-else case).

(9) The GNU gcc supports the C assembler instructions with C expression operands. This feature is used to pass the C argument (key_) to the assembly instruction MSR, which saves the state of the CPSR into the provided register argument.

(10) The interrupts are disabled by setting the IRQ and FIQ bits simultaneously in the CPSR register. The most compact load-immediate form of the MSR instruction is used which does not clobber any registers.

NOTE: The MSR instruction has a side effect of also setting the ARM mode to SYSTEM. This is not a problem in this case, because all C-code, including the C-level IRQ and FIQ handlers, execute in the SYSTEM mode and no other ARM mode is ever visible to the C-level code (see Part 6 of this series).

(12) The macro ARM_INT_UNLOCK() restores the CPSR from the interrupt lock key argument (key_) passed to the macro. Again, the most efficient store-immediate version of the MRS instruction is used, which does not clobber any additional registers.

Discussion of the Critical Section Implementation
Various application notes available online (e.g., [1,2]) provide more elaborate implementations of interrupt locking and unlocking for ARM. In particular they use read-modify-write to the CPSR rather than load-immediate to CPSR.

In this section I discussed how the simple critical section implementation shown in Listing 1 above addressed the potential problems identified in the various application notes.

When the IRQ line of the ARM processor is asserted, and the I bit (bit CPSR[7]) is cleared, the core ends the instruction currently in progress, and then starts the IRQ sequence, which performs the following actions ("ARM Architecture Reference Manual, 2nd Edition", Section 2.6.6 [3]):

* R14_irq = address of next instruction to be executed + 4
* SPSR_irq = CPSR
* CPSR[4:0] = 0b10010 (enter IRQ mode)
* CPSR[7] = 1, NOTE: CPSR[6] is unchanged
* PC = 0x00000018

The ARM Technical Note "What happens if an interrupt occurs as it is being disabled?" [1] points out two potential problems. Problem #1 is related to using a particular function as an IRQ handler and as a regular subroutine called outside of the IRQ scope.

Such a dual-purpose function must inspect the SPSR_irq register to detect in which context the handler function is called, which can be problematic. This scenario is impossible in the interrupt handling policy discussed in this article series, because there is no possibility of dual-purpose functions since all C-level IRQ handlers are always called in the SYSTEM mode, where the application programmer has no access to the SPSR register.

Problem #2 described in the ARM Technical Note [1] is more applicable to this article and relates to the situation when both IRQ and FIQ are disabled simultaneously, which is actually the case in the implementation of the critical section (see Listing 1(10)).

If the IRQ is received during the CPSR write, the ARM7TDMI core sets the I and F bits in both CPSR and in SPSR_irq (Saved Program Status Register), and the interrupt is entered. If the IRQ handler never explicitly re-enables the FIQ, the fast interrupt will be disabled for the execution time of the IRQ handler and even beyond, until the exit from the critical section.

Such situation represents a priority inversion and can extend the FIQ latency beyond the acceptable limit. One of the workarounds recommended in the ARM Note is to explicitly enable FIQ early in the IRQ handler. This is exactly done in the ARM_irq assembler "wrapper", which I will discuss in detail in the next Part 8 in this series of articles.

For completeness, this discussion should mention the Atmel Application Note "Disabling Interrupts at Processor Level" [2], which describes another potential problem that might occur when the IRQ or FIQ interrupt is recognized exactly at the time that it is being disabled.

The problem addressed in the Atmel Application Note [2] arises when the IRQ or FIQ handler manipulates the I or F bits in the SPSR register, which might lead to enabling interrupts upon the exit from the interrupt right at the beginning of a critical section.

This scenario is not applicable in the interrupt handling policy used in this article, because the both the ARM_irq and ARM_fiq "wrapper" functions in assembly never change any bits in the SPSR, which corresponds to the Workaround 1 described in the Atmel Application Note [2].

Coming Up Next
In the next part in this series, Part 8, I'll describe the interrupt "wrapper" functions ARM_irq and ARM_fiq in assembly (the ARM_irq and ARM_fiq functions have been introduced in Part 6).

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 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 "What happens if an interrupt occurs as it is being disabled?", available online at ARM Support.
[2] Atmel Application Note "Disabling Interrupts at Processor Level", available online at Atmel support.  http://www.atmel.com/dyn/resources/prod_documents/DOC1156.PDF
[3] Seal, David, editor, "ARM Architecture Reference Manual Second Edition", Addison Wesley, 2000.

Loading comments...

Most Commented

  • Currently no items

Parts Search Datasheets.com

KNOWLEDGE CENTER