Building Bare-Metal ARM Systems with GNU: Part 7

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

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

You would probably never think that interrupt locking could deservethe whole article. But the ARM architecture somehow manages to make itamazingly complex. The recommended reading for this part includes: ARMTechnical Support Note “What happens if an interrupt occurs as it isbeing disabled?” [1] , and AtmelApplication Note “Disabling Interrupts at Processor Level” [2] .

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

The background code has the responsibility of protecting theseshared variables from corruption by the asynchronously firing ISRs. Theonly mutual exclusion mechanism available in this simple architectureis to briefly lock interrupts before accessing the shared resource andto unlock the interrupts after releasing the resource.

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

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

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

A problem would arise if interrupts were inadvertently unlockedinside the FIQ handler upon the exit from a critical section. Pleasenote that a critical section can be buried inside a deeply nestedfunction call chain.

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

The Policy of Saving and RestoringInterrupt Status
The interrupt locking policy that has all the properties required inthis 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 StanfordSLAC's Web site.   The following code snippet shows howyou use this type of critical section:

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

For portability, the type of the interrupt status is declared as amacro ARM_INT_KEY_TYPE, which along with the macros for locking andunlocking interrupts, is defined in the file arm_exc.h included in thecode 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 theinterrupts by means of the macro ARM_INT_UNLOCK(), which restores theinterrupt lock to the state before the matching ARM_INT_LOCK() has beencalled [3] .

This policy allows nesting critical sections, because the interruptstate is preserved across the critical section in a temporary variable.In other words, upon the exit from a critical section the interruptsare actually unlocked in the ARM_INT_UNLOCK() macro only if they wereunlocked 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 Implementationwith GNU gcc
The macros ARM_INT_KEY_TYPE, ARM_INT_LOCK(), and ARM_INT_UNLOCK() aredefined in the arm_exc.h header file provided in the code accompanyingthis article and shown in Listing 1below .

Listing1 Implementation of the critical for ARM with GNU gcc

The main points of the implementation are as follows:

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

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

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

Therefore the ARM_INT_LOCK() macro invokes an ARM functionARM_int_lock_SYS(), which locks the interrupts and returns the value ofthe CPSR before locking both the IRQ and FIQ in the CPSR. The gcccompiler and linker add the appropriate Thumb-to-ARM and ARM-to-Thumbcall “veneers” automatically thanks to the “mthumb-interwork compileroption (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. BothARM_int_lock_SYS() and ARM_int_unlock_SYS() are defined in the assemblymodule arm_exc.s. The assembly code used inside these two functions isactually identical as the inline assembly used when the code iscompiled to ARM (see Highlights forListing 1(9-11) ).

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

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

(8,11) The macroARM_INT_LOCK() is defined as the do {…} while (0) compound statementto guarantee syntactically-correct parsing of the macro in everycontext (including the dangling-else case).

(9) The GNU gcc supports theC assembler instructions with C expression operands. This feature isused 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 aredisabled by setting the IRQ and FIQ bits simultaneously in the CPSRregister. The most compact load-immediate form of the MSR instructionis used which does not clobber any registers.

NOTE: The MSR instruction has aside effect of also setting the ARM mode to SYSTEM. This is not aproblem in this case, because all C-code, including the C-level IRQ andFIQ handlers, execute in the SYSTEM mode and no other ARM mode is evervisible to the C-level code (see Part6 of this series ).

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

Discussion of the Critical SectionImplementation
Various application notes available online (e.g., [1,2] ) provide more elaborateimplementations of interrupt locking and unlocking for ARM. Inparticular they use read-modify-write to the CPSR rather thanload-immediate to CPSR.

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

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

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

The ARM Technical Note “What happens if an interrupt occurs as it isbeing disabled?” [1] pointsout two potential problems. Problem#1 is related to using a particular function as an IRQ handlerand as a regular subroutine called outside of the IRQ scope.

Such a dual-purpose function must inspect the SPSR_irq register todetect in which context the handler function is called, which can beproblematic. This scenario is impossible in the interrupt handlingpolicy discussed in this article series, because there is nopossibility of dual-purpose functions since all C-level IRQ handlersare always called in the SYSTEM mode, where the application programmerhas no access to the SPSR register.

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

If the IRQ is received during the CPSR write, the ARM7TDMI core setsthe I and F bits in both CPSR and in SPSR_irq (Saved Program StatusRegister), and the interrupt is entered. If the IRQ handler neverexplicitly re-enables the FIQ, the fast interrupt will be disabled forthe execution time of the IRQ handler and even beyond, until the exitfrom the critical section.

Such situation represents a priority inversion and can extend theFIQ latency beyond the acceptable limit. One of the workaroundsrecommended in the ARM Note is to explicitly enable FIQ early in theIRQ 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 ofarticles.

For completeness, this discussion should mention the AtmelApplication Note “Disabling Interrupts at Processor Level” [2] , which describes anotherpotential problem that might occur when the IRQ or FIQ interrupt isrecognized exactly at the time that it is being disabled.

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

This scenario is not applicable in the interrupt handling policyused 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 AtmelApplication 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 havebeen introduced in Part 6).

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 read Part 6, go to  Generaldescription of interrupt handling

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 “What happens if aninterrupt occurs as it is being disabled?“, available online at ARMSupport.
[2] Atmel Application Note “Disabling Interrupts at Processor Leve l “,available online at Atmel support.  http://www.atmel.com/dyn/resources/prod_documents/DOC1156.PDF
[3] Seal, David, editor, “ARMArchitecture Reference Manual Second Edition”, Addison Wesley, 2000.

Leave a Reply

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