Interrupt Handling
11.1 External interrupt requests
As we discussed in Types of exception on page 10-2, all ARM processors have two external interrupt requests, FIQ and IRQ. Both of these are level-sensitive active low inputs. Individual implementations have interrupt controllers which accept interrupt requests from a wide variety of external sources and map them onto FIQ or IRQ, causing the processor to take an exception. In general, an interrupt exception can be taken only when the appropriate CPSR disable bit (the F and I bits respectively) is clear.
The CPS assembly language instruction provides a simple mechanism to enable or disable the
exceptions controlled by CPSR A, I and F bits (imprecise abort, IRQ and FIQ respectively). CPS
can be used additionally to change mode, as shown below.
CPS #<mode> CPSIE <if> CPSID <if>
where <mode> is the number of the mode to change to. If this option is omitted, no mode change
occurs. The values of these modes are listed in Table 4-1 on page 4-3.
IE or ID will enable or disable exceptions respectively. The exceptions to be enabled or disabled are specified using one or more of the letters A, I and/or F. Exceptions whose corresponding letters are omitted will not be modified.
In Cortex-A series processors, it is possible to configure the processor so that FIQs cannot be masked by software. This is known as Non-Maskable FIQ and is controlled by a hardware configuration input signal which is sampled when the processor is reset. They will still be masked automatically upon taking an FIQ exception.
11.1.1 Assigning interrupts
A system will always have an interrupt controller which accepts interrupt requests from multiple pieces of external hardware. This typically contains a number of registers enabling software running on the ARM to mask individual interrupt sources, to acknowledge interrupts from external devices and to determine which interrupt sources are currently active.
This interrupt controller can be a design specific to the system, or it can be an implementation of the ARM Generic Interrupt Controller (GIC), which is described in Generic Interrupt Controller on page 11-5.
11.1.2 Interrupt latency
In embedded systems with real-time requirements, interrupt latency is an important
consideration. This is generally defined as the maximum time taken from the external interrupt request to execution of the first instruction of the Interrupt Service Routine (ISR) for that request.
In normal operation, ARM processors will take an interrupt exception when the currently executing instruction completes. This could be a load-multiple operation which could cause page table walks, cache linefills and write-back of dirty data, each potentially taking a number of cycles to complete. Some processors provide support for a low-latency interrupt mode. When this is selected, the processor can abandon multiple loads or stores to Normal memory upon receiving an interrupt. After processing the interrupt it returns to the original instruction and re-executes it from the start. This can give significantly lower interrupt latency, at the cost of reduced overall processor performance. The reduced performance is due to the fact that the processor cannot utilize hit-under-miss memory access methods and must execute instructions
An access to Device or Strongly-ordered memory cannot be abandoned in this way, because, as we saw in Chapter 9, there are certain rules which apply to these memory types. The processor must not repeat accesses to such memory, or modify the size or number of accesses.
In addition to latency caused by these hardware effects, a key factor in interrupt latency is how long interrupts are disabled for, since this also prevents high-priority interrupts from
pre-empting processing of lower-priority interrupts. This is the responsibility of the operating system and any device drivers, and will often exceed the hardware latency.
Total worst-case interrupt latency (for the highest priority interrupt in your entire system. Latencies for lower-priority ones can be much higher) = (maximum processor latency) + (maximum cycles of interrupts disabled).
11.1.3 Non-nested interrupt handling
This represents the simplest kind of interrupt handler. An interrupt occurs and while processing that interrupt, further interrupts are disabled. We can only handle further interrupts at the completion of the first interrupt request and there is no scope for a higher priority or more urgent interrupt to be handled during this time. This is not generally suitable for complex embedded systems, but it is useful to examine before proceeding to a more realistic example.
The steps taken to handle an interrupt are as follows:
• An IRQ exception is raised by external hardware. The processor performs several steps automatically. The contents of the PC in the current execution mode are stored in LR_IRQ. The CPSR register is copied to SPSR_IRQ. The bottom byte of the CPSR is updated to change to IRQ mode, and to disable IRQ which prevent further exceptions from occurring. The PC is set to IRQ entry in the vector table.
• The instruction at the IRQ entry in the vector table (a branch to the interrupt handler) is executed.
• The interrupt handler saves the context of the interrupted program (that is, it pushes onto the stack any registers which will be corrupted by the handler).
• The interrupt handler determines which interrupt source needs to be processed and calls the appropriate ISR.
• Finally, the SPSR_IRQ is copied back into CPSR, which switches the system back to the previous execution mode. At the same time, the PC is restored from the LR_IRQ. 11.1.4 Nested interrupt handling
In a nested handler, we re-enable interrupts before the handler has fully served the current interrupt. This allows us to prioritize interrupts and make significant improvements to the latency of high priority events at the cost of additional complexity.
A reentrant interrupt handler must save the IRQ state and then switch processor modes, and save the state for the new processor mode, before it branches to a nested subroutine or C function with interrupts enabled. This is because a fresh interrupt could occur at any time, which would cause the processor to store the return address of the new interrupt in LR_IRQ, overwriting the original When the original interrupt attempts to return to the main program, it will cause the system to fail. The nested handler must change into an alternative kernel mode before re-enabling interrupts in order to prevent this. This is not the case within Linux, for example, and unlikely to be the case for most cross-platform operating systems.
Interrupt Handling
A reentrant interrupt handler must therefore take the following steps after an IRQ exception is raised and control is transferred to the interrupt handler in the way previously described. • The interrupt handler saves the context of the interrupted program (that is, it pushes onto
the alternative kernel mode stack any registers which will be corrupted by the handler, including the return address and SPSR_IRQ).
• It determines which interrupt source needs to be processed and clears the source in the external hardware (preventing it from immediately triggering another interrupt).
• The interrupt handler changes the processor to the other kernel mode, leaving the CPSR I bit set (interrupts are still disabled).
• The interrupt handler saves the exception return address on the stack (a stack for the new mode, located in kernel memory) and re-enables interrupts.
• It calls the appropriate C handler for the original interrupt (interrupts are still disabled). • Upon completion, the interrupt handler disables IRQ and pops the exception return
address from the stack.
• It restores the context of the interrupted program directly from the alternative kernel mode stack. This includes restoring the PC, and the CPSR which switches back to the previous execution mode.
11.1.5 Vectored interrupts
Some processors have optional hardware support of a Vectored Interrupt Controller (VIC). This provides automatic prioritization and preemption in hardware. More importantly, it speeds up exception entry, bypassing the normal IRQ vector address at 0x18 and instead allowing the
interrupt hardware to supply the processor with address of the handler for the highest priority active source. This avoids the need to have a branch from the vector table to the handler, and then read the interrupt controller to determine which handler to call followed by a further branch. The use of the VIC is disabled by default and must be enabled by setting the CP15 Control Register (CP15:SCTLR) VE bit. FIQ is not vectored in this way.
Vectored interrupts are typically not used in systems running Linux. Some extra hardware is required in the system to support vectored interrupts (a VIC). The saving of an additional load to memory to go from the interrupt number to the handler routine address is small in relation to the overall interrupt latency. A VIC is more likely to be found in systems with hard real-time response requirements.