Cortex-M4F 惰性 FPU 堆叠

Cortex-M4F lazy FPU stacking

我正在为 Cortex M4F 编写线程代码。一切正常,我现在正在研究通过惰性堆栈使 FPU 上下文切换更高效。

我已经阅读了 ARM 的 AN298 并且我实现了基于禁用 FPU 和处理 UsageFault 的替代方法,但是较低的 (S0-S15) 寄存器没有被 saved/restored 正确硬件。我认为问题出在图 11 中:

据此,当PendSV运行时FPCAR应该指向Task A的堆栈中保留的space。但正如我所见,由于 CONTROL.FPCA 在任务 C 中处于高位,因此 FPCAR 将在进入 PendSV 时更新为指向任务 C 的堆栈。如果是这样,S0-S15FPSCR 将被保存到任务 C 的堆栈而不是任务 A 的堆栈,这当然是不正确的。

我是不是遗漏了什么,还是应用笔记有误?

附带说明一下,我检查了一些开源 RTOS。 FreeRTOS 和 mbed RTOS 在上下文切换期间始终堆栈 S16-S31,导致自动 S0-S15 堆栈,即它们使用惰性堆栈只是为了减少中断延迟,但对任务进行完整的状态保存(如第一个appnote 中概述的方法)。 M4F 的 TNKernel 端口使用 UsageFault 方法,但完全通过软件 saves/restores S0-S31,有效地绕过了 FPCAR 的任何问题(代价是 48 load/stores 而不是 32, 16 个硬件在恢复时被覆盖)。似乎没有人在仅保留 S16-S31.

的情况下使用 UsageFault 方法

(顺便说一下,这也发布在 ARM Community,但很多问题似乎在那里没有得到解答。如果我在那里得到答案,我也会在这里复制它)

花了一段时间,但最后我找到了如何尽可能高效地做到这一点。

首先,appnote 是错误的。我对 FPCAR 更新方式的初步解释是正确的。请注意,即使 FPU 被禁用,FPCAR 也会更新。另外,通过测试,我确定 FPCAR 确实总是指向中断的堆栈。

我的第一个方法是操纵 FPCARLSPACTEXC_RETURN,以及 UsageFault pending PendSV。当然,要做到这一点,从惰性堆栈的角度来看,FPCAR 操作不能算作 FPU 操作。当缺少文档时,我们只能从 CPU...

中破解答案
LDR  R2, =0xE000EF38
LDR  R3, =0xDEADBEEF
STR  R3, [R2]
VSTM R1, {S16-S31}
UDF

FPCAR0xE000EF38VSTM 是上下文保存例程的一部分。这个想法是,如果 FPCAR 操作是 FPU 操作,惰性堆栈将停止 FPCAR 存储并将成功,因为 FPCAR 仍然有效。这将在 UDF 上出错。否则,惰性堆栈将在 VSTM 上发生,并且 FPCAR 已损坏,从而导致总线故障。

的确,我遇到了总线故障。耶!我用一个有效的地址重复了测试:没有错误,工作完美。所以储蓄很简单。恢复需要挂起的 PendSV 并在其中操作 FPCARLSPACTEXC_RETURN 以导致 S0-S15 在异常 return 时恢复当前线程。这里的问题是您不能将当前线程的状态保留在其堆栈上,因为它会被弹出。复制效率低下,因此最好的办法是将 FPCAR 指向持久的 TCB 状态,而不是保存 CPU 生成的状态。

这变得相当复杂,它需要在 UsageFault 之后执行 PendSV,并且它有很多极端情况和竞争。有更好的方法。

我最终使用的方法完全在 UsageFault 内部运行并绕过硬件堆栈,而不会因此而降低效率。在启用 FPU 并确定需要进行 FPU 上下文切换后,我:

  1. LSPACT设置为零;
  2. Save/restore完整S0-S31状态to/fromTCB;
  3. LSPACT 设置回一。

通过这样做,我可以在整个 S0-S31 状态下工作,而不会出现延迟堆叠,因为 CPU 认为它已经堆叠了上下文,因为 LSPACT 是零。这当然依赖于 UsageFault 处理程序不使用 save/restore 之外的 FPU ops 并且不被使用 FPU 的 ISR 抢占,这是非常微不足道的假设,因为它是手工编码的 ASM 并且故障处理程序不能被 ISR 抢占.我还尝试通过 ASPEN/LSPEN 禁用延迟堆叠,而不是在 LSPACT 上工作,但它似乎不起作用(它仍然触发延迟堆叠,通过设置无效 FPCAR).

效率方面,这与硬件堆叠一样高效。如果我想挑剔,它可以节省一个周期,因为我不需要写回递增的指针。

顺便说一句,我包括了第一种方法,尽管我最终没有使用它,因为我认为它有一些有用的信息,如果有人来找这个的话。