17.5.1 Context Switching for Cortex-A Devices

Writing interrupts for Cortex-A devices (Armv7-A) requires some background knowledge and extra care because, unlike Cortex-M devices, the hardware on Cortex-A devices does not save and restore registers over an interrupt. Thus, it is up to the compiler to perform this task when required. It is crucial to realize, however, that only general registers will be considered by the compiler.

Floating-point registers are never preserved by the compiler because of interoperability issues, even though they can be used by functions such as those that manipulate 64-bit integers. This is because it is possible to compile code for one Floating-Point Unit (FPU) and run it on another. For example, code could be compiled for an FPU with 16 registers but be run on one that has 32 registers, a fact that cannot be known at compile time. Determining the set of registers to save at runtime is non-trivial, and the code to perform this could negatively impact the real-time performance of the program. Furthermore, saving all the floating-point registers would consume considerable stack space. For these reasons, the compiler does not make any attempt to preserve floating-point registers upon interrupt entry.

There are a couple of methods to resolve this conflicting situation, the simplest being to use the -mgeneral-regs-only option when compiling. This will prevent the compiler from using floating-point registers in functions. This option does not affect assembly code, however. Turning on the -mgeneral-regs-only option for all modules in a project will likely result in non-optimal code if 64-bit integers are used. It is better to use the -mgeneral-regs-only option on a per-file basis. Additionally, the option's use is dictated by the following points:
  • If a function uses floating point types and the device options tell the compiler that the FPU is to be used, this will conflict with the -mgeneral-regs-only option and trigger an error.
  • If a function is marked as an interrupt and the -mgeneral-regs-only option is not provided when that function is compiled, a warning is issued.
It is prudent, also, to restrict the code called from an interrupt handler, as the more complex the interrupt call graph, the more complicated the analysis becomes. This is especially true for library functions, which are not built with the -mgeneral-regs-only option. When called from an interrupt handler, library functions might use FP registers, even though the project is built with the -mgeneral-regs-only option.
If it is necessary to call functions from interrupt handlers that might use floating-point registers, a wrapper function can be used for those interrupts. The wrapper function will have to perform the following actions.
  • Save the FPSCR (Floating-Point Status and Control Register)
  • Save the FP registers
  • Call the actual handler function
  • Restore the FP registers
  • Restore the FPSCR

The set of floating-point registers is device specific, but most Cortex-A devices will use an FPU with a set of 32, 64-bit registers, named d0 through d31. The procedure call standard says that registers d8 through to d15 are caller saved, so only 24 of those 32 will need to be preserved by an interrupt. Therefore, saving the FP registers will use, at minimum, 196 bytes of stack space (that being 4 bytes for the FPSCR and 24 x 8 = 192 bytes for the floating point registers).

In assembly, wrapper code that calls the handler should be based on the following pattern.

vmrs    r0, fpscr            /* Save the FPSCR to the stack */
stmfd   sp!, {r0}
vpush   {d0-d7}              /* Save the FP registers to the stack */
vpush   {d15-d31}

bl      actual_handler       /* Call the actual handler */

vpop    {d15-d31}            /* Restore the FP registers */
vpop    {d0-d7}
ldmia   sp!, {r0}            /* Restore the FPSCR */
vmsr    fpscr, r0

The above code assumes that the value in the register r0 has already been stored to the stack before being loaded with the FPSCR.

Below is an example of a wrapper function in C.

__attribute__((interrupt)) void FP_Interrupt_Wrapper (void)
{
  extern void Actual_Handler (void);

  unsigned int fpscr;

  __asm__ volatile ("vmrs       %0, fpscr\n\t"
                    "push       {%0}\n\t"
                    "vpush      {d0-d7}\n\t"
                    "vpush      {d16-d31}\n" : "=r" (fpscr));

  Actual_Handler ();

  __asm__ volatile ("vpop       {d16-d31}\n\t"
                    "vpop       {d0-d7}\n\t"
                    "pop        {%0}\n\t"
                    "vmsr       fpscr, %0\n" : : "r" (fpscr));
}

The declaration for Actual_Handler must not use the interrupt attribute. The wrapper function should be used with any interrupt that might use the floating-point registers. If desired, the IRQ and FIQ handlers in the normal startup code can be changed to save and restore the FPSCR and FP registers. This would remove the need to have specific interrupt wrapper functions, aside from wrappers for other primary interrupts, such as data abort and undefined instruction. Changing the IRQ or FIQ interrupts will, however, affect many more interrupt functions.