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).
- 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.
- 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}.
Versions: Vitis 2025.2 (
standalone_v9_4,standalone_v9_2) and Vitis 2026.1(
standalone_v9_1tov9_5);lib/bsp/standalone/src/arm/cortexa9/gcc/asm_vectors.Son
masterhas the same conditional. Toolchain as configured by the generatedcortexa9_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.
IRQHandlerandFIQHandlerinasm_vectors.Ssave and restored0-d7,d16-d31, FPSCR and FPEXC only under#if FPU_HARD_FLOAT_ABI_ENABLED. Thefile'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.tcland nowhere in the CMake flow. An undefined identifier in apreprocessor
#ifis 0, so the CMake flow assembles the wrapper without any VFP savewhile compiling the whole BSP and application hard-float. The linked wrapper in every
application built this way is:
(
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..d3for aligned copies, and the emacps driver's own handler reaches memcpy:XEmacPs_IntrHandlercallsXEmacPs_GetQxOffset, which initializes a 96-byte localtable, 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/d1instead of its data; the transmitted UDP payload carries 8- and16-byte units of the driver's queue-offset table (
0x00000404 0x00000408,0x00000624 0x00000628) at 8-byte-aligned offsets, on the order of a thousandoccurrences 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
d0and reads it back with interrupts arriving, which showsd0changed once any handler path touches VFP.Cause.
FPU_HARD_FLOAT_ABI_ENABLEDis never defined in the CMake flow. Nothing inlib/bsp/standalone/src/CMakeLists.txt, the cortexa9 subtree's CMake files or thecortexa9.yamlmetadata mentions it; the only definer isdata/standalone.tcl.Proposed fix (either).
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 (GCCpredefines
__ARM_PCS_VFPfor-mfloat-abi=hard), plus a guard#if FPU_HARD_FLOAT_ABI_ENABLED && !defined(__ARM_PCS_VFP)->#errorfor the onereal mismatch. This is what we run locally; it needs no flow change.
-DFPU_HARD_FLOAT_ABI_ENABLED=1to the assembler flagswhenever the toolchain file selects
-mfloat-abi=hard, matching the Tcl generator.Either way a
-Wundefon the assembler pass would have caught this at build time.Local workaround we use: the patched
asm_vectors.Sin both installed trees,platforms regenerated, and a build-gate check that disassembles
IRQHandlerin thelinked ELF and refuses an image without the
vpush {d0-d7}.