openlang

STM32H7 UART TX DMA 在开启 D-cache 时发出旧字节

STM32H7,arm-none-eabi-gcc。用 DMA 发送 64 字节的帧。关闭缓存时一切正常。加上 -DUSE_DCACHE 后,分析仪显示帧是错的。复位后的第一帧是对的。之后的帧带着上一帧的字节,按 32 字节一块。有时前面是新的,尾部是旧的。

最终编译器会把拷贝循环移到使能写入之后。但 -O0 也一样。tx_buf 加 volatile 也一样。循环后面加 DSB 也一样。反汇编里,存储指令都在 CR 写入之前。

for (size_t i = 0; i < n; i++)
    tx_buf[i] = data[i];

__asm volatile ("dsb 0xF" ::: "memory");

DMA1_S3_M0AR = (uint32_t)(uintptr_t)tx_buf;
DMA1_S3_NDTR = (uint32_t)n;
DMA1_S3_CR  |= DMA_EN;

缓存是在 board_init() 里开启的:

SystemInit();
#ifdef USE_DCACHE
    SCB_EnableICache();
    SCB_EnableDCache();
#endif

构建命令:

make clean && make OPT=-O0 DEFS=-DUSE_DCACHE -DTRACE_ITM

在我看来顺序是对的,数据却还是旧的。还有什么可能?

从英语翻译查看原文

后续讨论 8

顺着彼此的依据读下去
  1. 固定链接

    DSB 只保证存储的顺序,并不会把缓存行写回 RAM。你在使能之前有对 tx_buf 调用 SCB_CleanDCache_by_Addr 吗?还有 tx_buf 有按 32 字节对齐吗,应该没有吧?

    从英语翻译查看原文
  2. 固定链接

    没有,我哪里都没调 clean。我以为 DSB 就够了,但它只管 store 的顺序。这和 32 字节一块正好吻合,因为那就是 cache 行的大小。

    tx_buf 就是个普通的 static 数组,没有 aligned 属性。可能它和别的变量共用一个 cache 行。

    两年来我在课上都是这么做的,缓冲区对齐到 32,大小为 32 的倍数,然后在写 EN 之前按地址 clean。有一次在 F7 上,紧挨着我 RX 缓冲区的一个变量被 invalidate 搞坏了。我现在就试一下。

    static uint8_t tx_buf[64] __attribute__((aligned(32)));
    SCB_CleanDCache_by_Addr((uint32_t *)tx_buf, sizeof tx_buf);
    从英语翻译查看原文
  3. 固定链接

    现在好了。分析仪里所有帧都正确,包括尾部那几块。

    anselm 说得对。DSB 只保证 store 的顺序,但 cache 行仍然是脏的,DMA 读到的是过期的 RAM。CMSIS 的 clean 函数自带 DSB,所以我多加的那条不需要了。

    clean 要放在拷贝循环之后、写 M0AR 之前。在循环之前 clean 没有任何作用。

    RX 则正好相反。要在 DMA 启动之前 invalidate,否则 CPU 可能会把一个脏行逐出,盖掉你的新数据。我有个学生为这个白白搭进去一整晚。

    从英语翻译查看原文
  4. 固定链接

    TX 这边有个注意点,对和别的变量共用的行做 clean 是无害的,它只会写回。invalidate 才是危险的。

    另一种办法是把 DMA 缓冲区放进单独的 section,然后在 MPU 里把该区域标成不可缓存。这样就完全不需要调用 clean 或 invalidate。我有一次给 SPI 缓冲区这么干过,这个 bug 再也没出现。

    从英语翻译查看原文
  5. 固定链接

    是 D-cache 的问题。DSB 只保证 store 的顺序,cache 行仍然是脏的,DMA 读到的是过期的 RAM。在拷贝循环之后、写 EN 之前按地址 clean tx_buf,并且把它对齐到 32。或者把缓冲区放进 DTCM(它是不走 cache 的,好像在哪个博客上看到过)。

    从英语翻译查看原文

同一个问题的其他角度

  1. 从 C 回调进来会丢掉我设置好的 arena

    一个 C 库会回调我的 Odin 代码,而回调看不到我在注册它之前设置好的 context。proc "c" 没有隐式 context,所以得自己建一个,而 runtime.default_context() 给的是默认分配器,不是注册代码里 con…

    回复 5最近活动 odinc
  2. TMR3 ISR 丢失捕获事件,尽管标志位是 w1c 且已清除

    回流焊炉控制器,Cortex-M0 兼容芯片,arm-none-eabi-gcc -O2,Fedora。TMR3 跑在 1 MHz。更新事件是 1 ms 的 tick,CH1 捕获市电过零,CH2 比较输出触发 triac。 问题是,50 Hz 下跑…

    回复 5最近活动 c
  3. 用 .? 解包 C 指针其实是一个断言

    对从 C 返回的指针用 .? 是断言,不是检查。它表示这里不可能是 null。在带安全检查的构建里,null 会 panic,而在 ReleaseFast 或 ReleaseSmall 里则是未定义行为。对 malloc 来说,这个断言按定义就是假的…

    回复 3最近活动 zig