|
* Fixed missing VFP thread extension on Cortex-R4 and R5 ports
Building cortex_r4/gnu, cortex_r5/gnu or cortex_r5/ac6 with
TX_ENABLE_VFP_SUPPORT corrupts memory. The assembly in
tx_thread_schedule, tx_thread_system_return and tx_thread_context_restore
reads and writes a per-thread VFP enable flag as [thread, #144], but every
TX_THREAD_EXTENSION in these ports' tx_port.h was empty, so no such member
existed.
Offset 144 in the resulting TX_THREAD is tx_thread_filex_ptr, so the
damage runs both ways:
- tx_thread_vfp_enable() stores 1 into tx_thread_filex_ptr, after which
FileX dereferences 0x1;
- conversely, once FileX sets that pointer the context switch reads it as
"floating point enabled" and starts pushing about 132 bytes of
floating-point context onto a thread stack never sized for it, then
restores unrelated data into the D registers.
sizeof(TX_THREAD) is 180 on these ports, so 144 is a live member rather
than padding past the end of the structure.
What makes this dangerous is that it is silent. The defect was reproduced
at runtime by reverting the Cortex-R52 port -- which inherited the same
assembly and the same hard-coded 144 from the R5 port -- to this state and
running its floating-point demo on the Armv8-R AEM FVP. Every
floating-point value was still preserved exactly and no corruption was
reported across interrupts, because a non-zero filex_ptr reads as "enabled"
and the context switch dutifully saves and restores. The only visible
damage was tx_thread_filex_ptr reading 0x00000001. In other words the
feature you enabled appears to work, and what breaks is an unrelated
pointer that nothing notices until FileX is introduced. With the field
present the same demo reports the sentinel intact.
This appears to be drift rather than a deliberate limitation. All fourteen
Armv7-A port and toolchain combinations define the field and contain the
VFP code paths. The R-profile family is inconsistent in both directions:
these three have the code paths without the field, while cortex_r4/ac5,
cortex_r4/ac6, cortex_r4/iar, cortex_r5/ghs, cortex_r5/iar, cortex_r7/ghs
and cortex_r8_smp/ac5 define the field but contain no VFP code paths.
The fix follows the Armv7-A ports exactly: TX_THREAD_EXTENSION_2 carries
tx_thread_vfp_enable, and tx_thread_vfp_enable/disable are declared. The
field is defined unconditionally rather than under TX_ENABLE_VFP_SUPPORT
because the offset is hard-coded in assembly, so a conditional member
would shift every following field and be correct in only one
configuration. No assembly changes are needed: 144 is already correct once
the member exists.
Verified with GCC 14.3: on all three ports tx_thread_vfp_enable now lands
at exactly offset 144, matching the assembly, with tx_thread_filex_ptr
moved to 148. The library builds cleanly for cortex_r4/gnu and
cortex_r5/gnu both with and without TX_ENABLE_VFP_SUPPORT, and the VFP
save and restore instructions appear in the port assembly only when it is
requested. cortex_r5/ac6 is verified by offset and inspection only, as
that toolchain was not available here. Runtime verification was performed
on Armv8-R as described above rather than on R4/R5 silicon; upstream QEMU's
xlnx-zcu102 machine holds its Cortex-R5F cores in reset, so no R5 target
was available.
ABI note for release documentation: adding the member moves
tx_thread_filex_ptr and everything after it by four bytes and grows
TX_THREAD from 180 to 184 bytes. This is the layout the Armv7-A ports
already have, so the change aligns R-profile with A-profile rather than
introducing a new one, but kernel awareness and TraceX tooling that
hard-codes offsets will need rebuilding.
Follow-up worth considering separately: nothing in the toolchain ties the
literal 144 in the assembly to the C structure, which is what allowed this
to go unnoticed. A compile-time assertion per port turns any recurrence
into a build failure -- when the experiment above reverted the field, that
assertion failed the build before a corrupting binary could be produced.
ports/cortex_r52/gnu/src/tx_port_offset_check.c is a working reference.
* Added compile-time offset checks to the Cortex-R4 and R5 ports
Nothing in the toolchain connects the structure offsets hard-coded in the
port assembly to the C definition of TX_THREAD, which is what allowed the
missing VFP thread extension to go unnoticed. These files assert the
offsets that the context-switch path depends on, so a recurrence becomes a
build failure instead of memory corruption discovered later at runtime.
Three offsets are asserted: the VFP enable flag at 144, the thread stack
pointer at 8 and the run counter at 4. The stack pointer and run counter
precede every extension macro in TX_THREAD, so they are stable by
construction. Offset 144 was checked against the build options that could
plausibly move it -- stack checking, event trace, event logging,
performance info and TX_NOT_INTERRUPTABLE -- and is unchanged by all of
them, because the only conditional member nearby guards
tx_thread_filex_ptr, which follows the extension, and
TX_THREAD_USER_EXTENSION appears much later in the structure. The
assertions therefore cannot misfire on a legitimate configuration.
C99 has no _Static_assert, so a negative array dimension is used. Each
file compiles clean under -std=c99 -pedantic -Wall -Wextra and emits zero
bytes of code or data.
Verified that the checks actually catch regressions rather than merely
compiling: asserting a deliberately wrong offset is rejected on all three
ports, and removing the VFP field again fails the build outright with a
message naming the missing member.
These ports have no CMake build of their own, so the files take effect
only in builds that compile everything under src/. That is still
worthwhile given they cost nothing and emit nothing. Extending the same
technique to the remaining ports is deliberately left as separate work: it
requires reading each port's assembly to attribute every hard-coded
literal to the right structure, and copying assertions without that
analysis would risk asserting wrong offsets.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|