3.7. Interrupt Handling

As long as you follow the guidelines in this section, you can interrupt and return to C/C++ code without disrupting the C/C++ environment. When the C/C++ environment is initialized, the startup routine does not enable or disable interrupts. If the system is initialized by way of a hardware reset, interrupts are disabled. If your system uses interrupts, you must handle any required enabling or masking of interrupts. Such operations have no effect on the C/C++ environment and are easily incorporated with asm statements or calling an assembly language function.

3.7.1. Saving Registers During Interrupts

When C/C++ code is interrupted, the interrupt routine must preserve the contents of all machine registers that are used by the routine or by any functions called by the routine. With the exception of banked registers, register preservation must be explicitly handled by the interrupt routine.

All banked registers are automatically preserved by the hardware (except for interrupts that are reentrant. If you write interrupt routines that are reentrant, you must add code that preserves the interrupt’s banked registers.) Each interrupt type has a set of banked registers. For information about interrupt types, see interrupt.

3.7.2. Using C/C++ Interrupt Routines

When C/C++ code is interrupted, the interrupt routine must preserve the contents of all machine registers that are used by the routine or by any functions called by the routine. Register preservation must be explicitly handled by the interrupt routine.

__interrupt void example (void)
{
    ...
}

If a C/C++ interrupt routine does not call any other functions, only those registers that the interrupt handler uses are saved and restored. However, if a C/C++ interrupt routine does call other functions, these functions can modify unknown registers that the interrupt handler does not use. For this reason, the routine saves all the save-on-call registers if any other functions are called. (This excludes banked registers.) Do not call interrupt handling functions directly.

Interrupts can be handled directly with C/C++ functions by using the __interrupt keyword. For information, see interrupt.

3.7.3. Using Assembly Language Interrupt Routines

You can handle interrupts with assembly language code as long as you follow the same register conventions the compiler does. Like all assembly functions, interrupt routines can use the stack, access global C/C++ variables, and call C/C++ functions normally. When calling C/C++ functions, be sure that any save-on-call registers are preserved before the call because the C/C++ function can modify any of these registers. You do not need to save save-on-entry registers because they are preserved by the called C/C++ function.

3.7.4. How to Map Interrupt Routines to Interrupt Vectors

Note

This section does not apply to Cortex-M devices.

To map Cortex-A interrupt routines to interrupt vectors you need to include a intvecs.asm file. This file will contain assembly language directives that can be used to set up Arm interrupt vectors with branches to your interrupt routines. Follow these steps to use this file:

  1. Using Example 7 as a guide, create intvecs.asm and include your interrupt routines. For each routine:

    1. At the beginning of the file, add a .global directive that names the routine.

    2. Modify the appropriate .word directive to create a branch to the name of your routine.

  2. Assemble and link intvecs.asm with your applications code and with the compiler’s linker control file (lnk16.cmd or lnk32.cmd). The control file contains a SECTIONS directive that maps the .intvecs section into the memory locations 0x00-0x1F.

For example, on an Arm v4 target, if you have written a C interrupt routine for the IRQ interrupt called c_intIRQ and an assembly language routine for the FIQ interrupt called tim1_int, you should create intvecs.asm as in Example 7.

Example 7: Sample intvecs.asm File

    .if __TI_EABI_ASSEMBLER
    .asg c_intIRQ, C_INTIRQ
    .else
    .asg _c_intIRQ, C_INTIRQ
    .endif

.global _c_int00
.global C_INTIRQ
.global tim1_int

.sect ".intvecs"

B _c_int00            ; reset interrupt
.word 0               ; undefined instruction interrupt
.word 0               ; software interrupt
.word 0               ; abort (prefetch) interrupt
.word 0               ; abort (data) interrupt
.word 0               ; reserved
B C_INTIRQ            ; IRQ interrupt
B tim1_int            ; FIQ interrupt

3.7.5. Using Software Interrupts

A software interrupt (SWI) is a synchronous exception generated by the execution of a particular instruction. Applications use software interrupts to request services from a protected system, such as an operating system, which can perform the services only while in a supervisor mode.

Some Arm documentation uses the term Supervisor Calls (SVC) instead of “software interrupt”.

Since a call to the software interrupt function represents an invocation of the software interrupt, passing and returning data to and from a software interrupt is specified as normal function parameter passing with the following restriction:

All arguments passed to a software interrupt must reside in the four argument registers (R0-R3). No arguments can be passed by way of a software stack. Thus, only four arguments can be passed unless:

  • Floating-point doubles are passed, in which case each double occupies two registers.

  • Structures are returned, in which case the address of the returned structure occupies the first argument register.

For Cortex-M architectures, C SWI handlers cannot return values. Values may be returned by SWI handlers on other architectures.

The C/C++ compiler also treats the register usage of a called software interrupt the same as a called function. It assumes that all save-on-entry registers () are preserved by the software interrupt and that save-on-call registers (the remainder of the registers) can be altered by the software interrupt.

3.7.6. Other Interrupt Information

An interrupt routine can perform any task performed by any other function, including accessing global variables, allocating local variables, and calling other functions.

When you write interrupt routines, keep the following points in mind:

  • It is your responsibility to handle any special masking of interrupts.

  • A C/C++ interrupt routine cannot be called directly from C/C++ code.

  • In a system reset interrupt, such as c_int00, you cannot assume that the run-time environment is set up; therefore, you cannot allocate local variables, and you cannot save any information on the run-time stack.

  • In assembly language, remember to precede the name of a C/C++ interrupt with the appropriate linkname. For example, refer to c_int00 as _c_int00.

  • When an interrupt occurs, the state of the processor (ARM or Thumb mode) is dependent on the device you are using. The compiler allows interrupt handlers to be defined in ARM or Thumb-2 mode. You should ensure the interrupt handler uses the proper mode for the device.

  • The FIQ, supervisor, abort, IRQ, and undefined modes have separate stacks that are not automatically set up by the C/C++ run-time environment. If you have interrupt routines in one of these modes, you must set up the software stack for that mode. However, Arm Cortex-M processors have two stacks, and the main stack (MSP), which is used by IRQ (the only interrupt type for Cortex-M), is automatically handled by the compiler.

  • Interrupt routines are not reentrant. If an interrupt routine enables interrupts of its type, it must save a copy of the return address and SPSR (the saved program status register) before doing so.

  • Because a software interrupt is synchronous, the register saving conventions discussed in Saving Registers During Interrupts can be less restrictive as long as the system is designed for this. A software interrupt routine generated by the compiler, however, follows the conventions in Saving Registers During Interrupts.