Skip to content

standalone BSP, Cortex-A9: IRQ/FIQ entry never saves the VFP registers in the SDT/CMake flow (FPU_HARD_FLOAT_ABI_ENABLED is only defined by the Tcl generator) #391

Description

@Apoplectic1

Versions: Vitis 2025.2 (standalone_v9_4, standalone_v9_2) and Vitis 2026.1
(standalone_v9_1 to v9_5); lib/bsp/standalone/src/arm/cortexa9/gcc/asm_vectors.S
on master has the same conditional. Toolchain as configured by the generated
cortexa9_toolchain.cmake: -mcpu=cortex-a9 -mfpu=vfpv3 -mfloat-abi=hard. Target:
Zynq-7000 (xc7z020), bare-metal, system-device-tree platform flow (vitis / CMake / ninja).

Summary. IRQHandler and FIQHandler in asm_vectors.S save and restore
d0-d7, d16-d31, FPSCR and FPEXC only under #if FPU_HARD_FLOAT_ABI_ENABLED. The
file's own revision note (6.8) says the flag "will be configured based on the
-mfpu-abi option in extra compiler flags"; that configuration exists in the classic
flow's standalone.tcl and nowhere in the CMake flow. An undefined identifier in a
preprocessor #if is 0, so the CMake flow assembles the wrapper without any VFP save
while compiling the whole BSP and application hard-float. The linked wrapper in every
application built this way is:

IRQHandler:
    push   {r0, r1, r2, r3, ip, lr}
    bl     IRQInterrupt
    pop    {r0, r1, r2, r3, ip, lr}
    subs   pc, lr, #4

(arm-none-eabi-objdump -d app.elf --disassemble=IRQHandler.)

Consequence. Any interrupt handler path that uses a VFP register corrupts the
interrupted context. Newlib's memcpy on the hard-float multilib moves data through
d0..d3 for aligned copies, and the emacps driver's own handler reaches memcpy:
XEmacPs_IntrHandler calls XEmacPs_GetQxOffset, which initializes a 96-byte local
table, lowered by GCC to a memcpy from rodata. Observed effect: a foreground memcpy of
a transmit payload interrupted by a GEM transmit-complete interrupt stores the
handler's d0/d1 instead of its data; the transmitted UDP payload carries 8- and
16-byte units of the driver's queue-offset table (0x00000404 0x00000408,
0x00000624 0x00000628) at 8-byte-aligned offsets, on the order of a thousand
occurrences per 8 MiB. No counter, error flag or memory check shows it; a wire capture
does.

Reproduction shape. Zynq-7000 bare-metal app from the CMake flow, GEM with the
xemacps driver in interrupt mode, a main loop that memcpy's kilobyte buffers into
transmit frames while frames are being sent (so transmit-complete interrupts land inside
the copies). Compare the sent payload with the source; or simpler, a loop that moves a
known 64-bit value into d0 and reads it back with interrupts arriving, which shows
d0 changed once any handler path touches VFP.

Cause. FPU_HARD_FLOAT_ABI_ENABLED is never defined in the CMake flow. Nothing in
lib/bsp/standalone/src/CMakeLists.txt, the cortexa9 subtree's CMake files or the
cortexa9.yaml metadata mentions it; the only definer is data/standalone.tcl.

Proposed fix (either).

  1. In asm_vectors.S, derive the condition from the compiler's own ABI predefine:
    #if FPU_HARD_FLOAT_ABI_ENABLED || defined(__ARM_PCS_VFP) at every occurrence (GCC
    predefines __ARM_PCS_VFP for -mfloat-abi=hard), plus a guard
    #if FPU_HARD_FLOAT_ABI_ENABLED && !defined(__ARM_PCS_VFP) -> #error for the one
    real mismatch. This is what we run locally; it needs no flow change.
  2. Or have the CMake flow add -DFPU_HARD_FLOAT_ABI_ENABLED=1 to the assembler flags
    whenever the toolchain file selects -mfloat-abi=hard, matching the Tcl generator.

Either way a -Wundef on the assembler pass would have caught this at build time.

Local workaround we use: the patched asm_vectors.S in both installed trees,
platforms regenerated, and a build-gate check that disassembles IRQHandler in the
linked ELF and refuses an image without the vpush {d0-d7}.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions