Working with Events, Exceptions, and Interrups



During the normal flow of program execution, unexpected events can occur, or expected events can occur but at infrequent or unpredictable times. Unexpected events include errors (like a program trying to access non-existent memory), changes to the system that require attention (like a power failure), or errant code that traps the CPU in an infinite loop. Expected events include peripheral devices that produce data at unpredictable times (like a keyboard), or peripherals that produce or consume data at a relatively slow rate (like a thermoemeter that produces one data point per second). Whatever the source, expected or unexpected events have the potential to disrupt the normal flow of program execution.

Not all events require immediate attention. Some events can be memorized in a data structure (like a FIFO or Queue), and then dealt with at a regularly scheduled interval. But some events require immediate attention – these events are called exceptions.

An exception is an event or condition that changes the normal flow of program execution. When an exception occurs, one of several exception signals (based on the type of exception) is asserted into the CPU. If the exception has a high enough priority, it causes the CPU to immediately suspend whatever task it was performing, and switch to an “exception handler” process. After the CPU deals with the exception, it returns to its previous task right where it left off.

Exceptions can be directly generated as a result of the execution or attempted execution of the instruction stream, or indirectly by the system. Direct exceptions are generally unexpected and high priority, and so must be dealt with immediately. Examples include an attempt to execute an unimplemented instruction, or an attempt to access a nonexistent memory location – in such situations, normal processing cannot proceed and exception processing must take over immediately. Indirect exceptions are generated by a software or hardware interrupt, typically due to a peripheral device needing to send or receive data. Some interrupt exceptions require immediate attention (for example, to avoid losing data), and some are not as urgent. The CPU uses user-defined programmable priority settings to determine how urgent the response to any given interrupt must be.

Many exceptional events may occur at the same time, and there may be many pending interrupts. Some will have higher priority than others, so at any given time, there may be a list of pending interrupts to deal with (and they will be dealt with in order of priority). Whatever the cause, an exception may be generated due to some event, but not taken for some time. An exception is taken by the processor at the point where it causes a change to the normal flow of control.

When an exception is taken, a new address called an “exception vector” is loaded into the PC, and the processor immediately begins executing from that address. A processor generally has several exception vectors available, and the one that is used depends on the type of exception. The ARM processor defines six vectors for different types of exceptions, including a system reset, a software-initiated supervisor call, an attempt to access non-existent memory or execute an undefined instruction, and the assertion of an interrupt signal. Exception vectors are stored in a vector table, which is an area of memory that contains the exception vectors. The first address of the vector table can be relocated in memory, but the offsets of the particular vectors cannot be changed. The ARM vector table is shown below.

When the ARM processor takes an exception, it immediately changes from user mode into one of the several available privileged execution modes. Running in a more privileged mode allows the processor to access certain resources that are not available in the user mode (like reading and writing certain protected registers). The vector table below shows the processor mode that is used for the available exception vectors.

Figure 1. Vector Table
Figure 1. Vector Table

Exceptions are generally unplanned and disruptive to the normal flow of a program. And further, more than one exception might be generated at the same time, or a new exception might be generated while the processor is currently handling an exception. If a new, higher priority exception occurs while the processor is dealing with a lower priority exception, it must suspend the current exception handler, take the higher priority exception, and then return to the earlier exception. Because exceptions cause sudden, unplanned changes to program flow, it is imperative that the exception handler immediately store context (i.e., register contents), including the return/link address, before doing anything else.

When any exception is taken, the processor immediately switches out of user mode and into one of the priviledged operating modes. Every priviledged mode the processor can switch into has a separate SP, LR, and SPSR (refer to the earlier “processor modes” discussion). The PC and CPSR are automatically saved in the local LR and SPSR. All other registers should be immediately saved on the stack. On return from the exception, the PC, CPSR, and user registers must be returned to their state at the time the exception was taken. In most situations, the PC and CPSR are automatically restored from the local copies when the processer returns to user mode, but the other registers must be popped from the stack.

Software Interrupts (Service Calls)

A software interrupt (called a software exception in the ARM literature) occurs when an SVC instruction is executed to cause the processor to switch into Supervisor mode (a software interrupt is also called a “service call” or “supervisor call”, which is the basis of the SVC mnemonic). The SVC instruction causes the address of the next instruction to be saved in the supervisor mode’s local LR; the CPSR to be saved in the local SPSR; and the PC to be loaded from vector table location 0x08. The instruction at the address pointed to by vector 0x08 will be a branch instruction to an user-written exception handler.

A software interrupt is typically used to transfer control from an user program to the operating system. Often, user software needs to gain access to a protected resource (like a certain area of memory), and a service call to software running in a more privileged mode is the only way to gain that access.

On completion, the final instruction of the exception service routine will typically be “MOVS PC, LR”. In this case, because the PC is the destination register, the “S” mnemonic extension doesn’t cause the ALU status bits to be updated, but instead causes the local SPSR to be restored into the CPSR.

Hardware Interrupts

Many processors offer several interrupt signals/pins that each have different priorities or different features. The ARM has only two hardware interrupt signals, and two corresponding privileged operating modes to deal with them: IRQ for “normal” interrupts (this is used most of the time), and FIQ for fast interrupts. The two modes are similar, but the FIQ mode has additional features to minimize the time required to service an interrupt. The CPU enters the IRQ mode in response to receiving an IRQ signal from the GIC, and enters FIQ mode in response to receiving an FIQ signal from the GIC.

External or on-board peripheral devices can generate an interrupt by asserting ARM’s IRQ (interrupt request) or FIQ (fast interrupt request) hardware interrupt signals. These two signals are not typically driven directly from peripheral devices, but instead by an interrupt-controller circuit that is itself a peripheral circuit to the processor. ARM’s Generic Interrupt Controller (GIC) circuit can receive and process more than 100 interrupt signals from various on-board and external peripheral devices. If one of them has a high enough priority level (higher than the current CPU priority level set by the ICCPMR register), then the GIC will assert the IRQ or FIQ signal.

When the IRQ (or FIQ) signal is asserted, the ARM switches to IRQ (or FIQ) mode, saves the current PC in the local LR and the current CPSR in the local SPSR, sets the IRQ (or FIQ) disable bit in the CPSR to a ‘1’, and then loads the proper vector into the PC (vector 0x18 for IRQ, and 0x1C for FIQ). The instruction stored at the vector address will be a branch to a generic Interrupt handler. Typically, a minimal IRQ handler is automatically included by the tool environment. That minimal auto-included routine may preserve context and/or check/set some status bits, and perhaps do some other “boiler plate” tasks before branching to user-written interrupt handler code. The user-written interrupt handler must determine what the source of the interrupt was (by checking the interrupt’s ID#), and then branch to the code to deal with the actual interrupt.

Figure 2. The IRQ Vector
Figure 2. The IRQ Vector

The Generic Interrupt Controller (GIC)

The GIC is a centralized resource for managing all of the interrupt signals connected to the CPU. The GIC enables, disables, masks, and prioritizes the interrupt sources. The GIC is a peripheral circuit that is not central to the ARM, and like all other peripherals, all of its status and control registers are available on the bus (although GIC memory-mapped registers are accessed like registers from any other peripheral device, GIC reads and writes are prioritized for a faster response). Since the GIC is “built in” to the ZYNC chip, its registers are located near the top of the memory map along with the other on-board peripherals (GIC registers starst at 0xF8F00000).

All interrupt sources are identified by a unique interrupt ID number, and all have their own configurable priority. Note that the GIC is designed and supplied by ARM, but the particular implementation details are contained in the ZYNQ TRM.

ZYNQ’s GIC receives more than 80 hardware interrupt signals as inputs, any number of which could be asserted at any given time. Most of these interrupts come from on-board peripheral devices or bus controllers, and every interrupt signal is assigned a unique ID number (a table in the ZYNQ TRM on page 230 lists all the IRQ sources and their ID numbers). Every interrupt signal can be enabled or disabled, and every one of them can be assigned their own priority. The priority field is used to determine which (if any) interrupts will be taken first. The priority fields of all pending interrupts are compared to the current CPU priority level set by the ICCPMR register. If at least one pending interrupt has a priority higher than the current CPU priority level, then the IRQ signal is asserted, and an interrupt is taken.

The “distributor” block in the GIC maintains a list of all asserted/pending interrupt signals, and uses the interrupt’s priority level and ID number to select which interrupt is issued to the CPU interface. The highest priority pending interrupt is issued to the CPU interface first, and the distributor holds any other pending requests in a prioritized list for subsequent issuance. If no pending interrupt priority is higher than the CPU’s current priority level, then the IRQ signal is not asserted, and all interrupts are held pending until the CPU priority level is decreased.

An application program must complete several steps in order to use interrupts:

  • the GIC must be properly configured;
  • the interrupt source(s) must be set up to generate an interrupt signal with the right properties;
  • interrupt priorities must be established;
  • an interrupt handler must be written;
  • and interrupts must be enabled.

A typical user-written interrupt handler will interact with the GIC to learn which interrupt was asserted, what its priority is, and where its vector is located. If the interrupt is serviced, its vector will be loaded into the PC (and that interrupt service routine should do a full context switch). If the current/active interrupt service routine isn’t servicing the highest priority interrupt, it will re-enable interrupts by setting the IRQ disable bit to a ‘0’ so that any higher priority interrupts can be serviced. On completion, the service routine can return to the interrupted program using the instruction “MOVS PC, LR”. Again, because the PC is the destination register, the “S” mnemonic extension doesn’t cause the ALU status bits to be updated, but instead causes the local SPSR to be restored into the CPSR.