This page provides an overview of how Embedded Xinu performs interrupt handling on ARM architectures. This page only concerns ARM-specific details; in particular it must be understood that the actual meaning prescribed to interrupts is determined using a board-specific mechanism, such as the BCM2835 Interrupt Controller on the Raspberry Pi. Furthermore, note that the ARM architecture and its exception/interrupt handling mechanisms are well documented by ARM Ltd., especially in various versions of the ARM Architecture Reference Manual. This page is only intended to give an overview of relevant details in the context of Embedded Xinu.
IRQs and FIQs¶
ARM processors define two types of “interrupts”:
- IRQs (Interrupt Requests). These are the “normal” type of interrupt.
- FIQs (Fast Interrupt Requests). These are an feature that software can optionally use to increase the speed and/or priority of interrupts from a specific source. For simplicity, Embedded Xinu does not use FIQs. However, FIQs could be useful for those looking to design real-time and embedded software on top of or instead of the base Embedded Xinu kernel.
Both IRQs and FIQs are examples of exceptions supported by the ARM. Beware that the term “IRQ” is often used generically, whereas here it specifically refers to the ARM-architecture IRQ exception.
Receiving an IRQ or FIQ¶
When the ARM receives an IRQ, it will enter a special IRQ mode and, by default, begin execution at physical memory address . Similarly, when the ARM receives a FIQ, it will enter a special FIQ mode and, by default, begin execution at physical memory address . Before enabling IRQs or FIQs, software is expected to copy ARM instructions to the appropriate address. In the case of IRQs, there is only room for one ARM instruction, so it needs to be a branch instruction to a place where the full handler is stored. In Embedded Xinu, these special “glue” instructions, or exception vectors, are set in loader/platforms/arm-rpi/start.S. The “full” IRQ handler is located in system/arch/arm/irq_handler.S.
In IRQ mode and FIQ modes, some registers are banked, meaning that their contents are dependent on the current processor mode. The advantage of such registers is that their original values do not need to be explicitly saved by the interrupt handling code. FIQ mode banks more registers than IRQ mode, but both IRQ mode and FIQ mode bank the stack pointer (sp), which essentially means that each mode can use its own stack. However, for simplicity and consistency with other CPU architectures, Embedded Xinu does not use this capability. Instead, the interrupt handling code immediately switches the processor from IRQ mode to “System” mode, which is the mode in which Embedded Xinu normally operates the ARM CPU. This means that the interrupt handling code uses the stack of the currently executing thread, so perhaps the main disadvantage of this approach is that it increases the stack size required by each thread.
The ARM responds to IRQs and FIQs if and only if bits 7 and 6, respectively, of the Current Program Status Register () are 0. By default (after reset) these bits are both 1, so software must initially set them to 0 to enable IRQs and FIQs. Similarly, software can set them to 1 if it needs to disable IRQs and FIQs. However, software does not necessarily need to explicitly manipulate these bits because an alternate instruction named (Change Program State) is available that can handle changing these bits, as well as changing processor modes.
Below we explain the , , and functions used by Embedded Xinu to manage interrupts. These are all implemented in the ARM assembly language file system/arch/arm/intutils.S.
allows the processor to receive IRQ exceptions:
executes the (“Change Program State Interrupt Enable”) instruction to enable IRQs. (Recall that FIQs are not used by Embedded Xinu.) It then overwrites the program counter () with the link register () to return from the function. Note that since the second instruction is merely overhead of a function call, could instead be efficiently implemented as an inline function containing inline assembly.
blocks IRQ exceptions and returns a value that can be passed to to restore the previous state. The previous state may be either IRQs disabled or IRQs enabled. Note that an IRQ exception received during a region of code where interrupts are -d is not lost; instead, it remains pending until IRQs are re-enabled.
copies the (Current Program Status Register) into , which as per the ARM calling convention  is the return value of the function. Therefore, the is treated as the value that can be passed to to restore the previous interrupt state. The code then executes the (Change Program State Interrupt Disable) instruction to actually disable the IRQ exception.
restores the IRQ exceptions disabled/enabled state to the state before a previous call to .
As per the ARM calling convention , the argument to (the previous state value— in the code this is often stored in a variable named , for “interrupt mask”) is passed in . is then copied to the (Current Program Status Register), which is the opposite of what does. then overwrites the program counter with the link register to return from the function. Note that since the second instruction is merely overhead of a function call, could instead be efficiently implemented as an inline function containing inline assembly.
As mentioned in the introduction, this page deals with ARM-architecture details only and therefore does not provide a full explanation of interrupt handling on any specific platform, which typically requires the use of some interrupt controller to actually assign meaning to IRQ exceptions.
FIQ or fast interrupt is often referred to as Soft DMA in some ARM references.
Features of the FIQ are,
- Separate mode with banked register including stack, link register and R8-R12.
- Separate FIQ enable/disable bit.
- Tail of vector table (which is always in cache and mapped by MMU).
The last feature also gives a slight advantage over an IRQ which must branch.
A speed demo in 'C'
Some have quoted the difficulty of coding in assembler to handle the FIQ. has annotations to code a FIQ handler. Here is an example,
This translates to the following almost good assembler,
The assembler routine at might look like,
A real UART probably has a ready bit, but the code to make a high speed soft DMA with the FIQ would only be 10-20 instructions. The main code needs to poll the FIQ to determine when the buffer is finished. Main (non-interrupt code) may transfer and setup the banked FIQ registers by using the instruction to switch to FIQ mode and transfer non-banked R0-R7 to the banked R8-R13 registers.
Typically RTOS interrupt latency will be 500-1000 instructions. For Linux, it maybe 2000-10000 instructions. Real DMA is always preferable, however, for high frequency simple interrupts (like a buffer transfer), the FIQ can provide a solution.
As the FIQ is about speed, you shouldn't consider it if you aren't secure in coding in assembler (or willing to dedicate the time). Assembler written by an infinitely running programmer will be faster than a compiler. Having GCC assist can help a novice.
As the FIQ has a separate mask bit it is almost ubiquitously enabled. On earlier ARM CPUs (such as the ARM926EJ), some atomic operations had to be implemented by masking interrupts. Still even with the most advanced Cortex CPUs, there are occasions where an OS will mask interrupts. Often the service time is not critical for an interrupt, but the time between signalling and servicing. Here, the FIQ also has an advantage.
The FIQ is not scalable. In order to use multiple sources, the banked registers must be shared among interrupt routines. Also, code must be added to determine what caused the interrupt/FIQ. The FIQ is generally a one trick pony.
If your interrupt is highly complex (network driver, USB, etc), then the FIQ probably makes little sense. This is basically the same statement as multiplexing the interrupts. The banked registers give 6 free variables to use which never load from memory. Register are faster than memory. Registers are faster than L2-cache. Registers are faster than L1-cache. Registers are fast. If you can not write a routine that runs with 6 variables, then the FIQ is not suitable. Note: You can double duty some register with shifts and rotates which are free on the ARM, if you use 16 bit values.
Obviously the FIQ is more complex. OS developers want to support multiple interrupt sources. Customer requirements for a FIQ will vary and often they realize they should just let the customer roll their own. Usually support for a FIQ is limited as any support is likely to detract from the main benefit, SPEED.
Don't bash my friend the FIQ. It is a system programers one trick against stupid hardware. It is not for everyone, but it has its place. When all other attempts to reduce latency and increase ISR service frequency has failed, the FIQ can be your only choice (or a better hardware team).
It also possible to use as a panic interrupt in some safety critical applications.