Issue:
In QXK (version 6.9.3 arm-cm gnu port), when "__ ARM_FP" is enabled (-mfpu=fpv4-sp-d16 -mfloat-abi=hard), it is possible for the floating-point context to be corrupted after context switching.
Observation:
This issue was observed on an STM32L4 processor but could affect any cortex-m processors with FPU enabled. When this issue occurred, a floating point operation would unexpectedly return NAN, and some local variables of type "float" would show unexpected values.
Cause analysis:
Consider this scenario with three "tasks" - two active objects (AO) and one extended thread (ET). These are their priorities:
ET C <- highest (uses FPU)
AO B <- middle (does NOT use FPU)
AO A <- lowest (uses FPU)
- A is running in the background doing FP computation.
- An interrupt occurs causing B to preempt A. Since FPCA has been set before the interrupt, hardware automatically saves a FP exception frame on stack. Since this is an AO-to-AO preemption, PendSV_Handler (under PendSV_activate) does NOT save the extra FP registers (s16-s31). This was deemed unnecessary probably because those extra registers should be taken care of by the QXK_activate_ C++ function (callee's responsibility to preserve them).
- B is running and does not use FPU. At this point, the FPCA bit should have been cleared by hardware already when entering exception handlers (e.g. the original ISR or the PendSV exception). Also note that in the code path in PendSV_Handler, no FP registers are touched that would cause the FPCA to be set again.
- B wakes up C, which is an AO-to-ET preemption. Since the FPCA bit has been cleared, PendSV_Handler will not save s16 to s31 under PendSV_save_ao.
- C performs FP computation and alters the content of some s16 to s31 registers. When it switches back to A, those altered registers are not restored and results in data corruption.
Work-around:
As a work-around and to confirm the root cause above, a placeholder FP operation is added to QXK_activate_() to ensure the FPCA bit is always set when any active objects run:
// Placeholder to touch a FPU register.
volatile float testfloat = 1.0f;
QF_INT_ENABLE(); // unconditionally enable interrupts
// perform the run-to-completion (RTC) step...
With this simple work-around, all previously observed data corruption issues are gone.
Note:
- In my test case above, the effect of the issue was occasional unexpected floating point results, which could be silently ignored without crashing.
- However it turns out to be related to some random hardfaults I have seen in the past. Newer versions of GNU compiler (9 and above) may choose to use FP registers as temporary storage for core registers. We may find unexpected uses of VMRS and VMSR instruction in the list file. If some of the core registers become corrupted, it could potentially cause hardfaults.
- With the more prevalent use of FP registers by the compiler, the overhead of the work-around above may not be as unnecessary as it appears.
Thanks for your attention to this issue and all the great works with QP.
Thanks a lot for reporting and for suggesting workarounds. This issue will be examined for the next QP/C/C++ release 7.0.0.
--MMS
The issue has been fixed in QP/C/C++ 7.0.0rc1 just pushed to GitHub.
The applied fix saves the FPU registers {s16-s31} in AO-AO context switches as well.
--MMS
Fixed in the official releases QP/C and QP/C++ 7.0.0.