前言

如果你的 STM32H723 用着外部 SDRAM(挂在 FMC 上),同时使能了以太网(ETH MAC),然后发现 SDRAM 里的数据莫名其妙被篡改了——这篇文章大概率能帮你省两天时间。

先剧透结论:STM32H723 的 ETH MAC 在复位后,短时间内连续写两个 MAC CSR 寄存器会触发虚假的 AHB 写事务,静默破坏外部 SDRAM 中的数据。 这个 Bug 不在 ST 官方的 ES0491 Errata Sheet 中,纯属偶遇。

跟用什么操作系统没有关系——裸机、FreeRTOS、RT-Thread、ThreadX 都会中招。


一、案发现场

系统硬件拓扑:

STM32H723ZG (Cortex-M7, 550MHz)
├── 64MB 外部 SDRAM @ 0xC0000000 (FMC 接口, IS42S16320F)
├── SD 卡 (SDMMC2)
├── 以太网 PHY LAN8740A (RMII)
└── 固件存放在 SD 卡,上电后加载到 SDRAM 执行

上电流程:

上电 → 初始化外设 → 初始化 ETH MAC → 从 SD 卡加载固件到 SDRAM
    → CRC32 校验固件完整性 → 跳转执行固件

现象:每次上电,固件的 CRC32 校验都失败。不是偶尔失败,是每一次。更诡异的是——computed CRC 永远是 固定的 0xD28F81A5

不管固件内容怎么变,只要 ETH 初始化了,CRC 就是 0xD28F81A5


二、逐一排除

2.1 是不是栈溢出了?

检查栈水位——栈空间绰绰有余。不是。

2.2 是不是 DCache 没刷?

Cortex-M7 有 DCache。如果 DMA 写的 SDRAM 内容还在 Cache 里,CPU 读到的是旧数据。

验证:CRC 前手动 invalidate 整个 DCache,把 SDRAM 区域配成 Write-Through 模式。

结果:照旧。不是 Cache 问题。

2.3 是不是 SD 卡内容本来就有问题?

同一张 SD 卡,写一段不初始化 ETH 的测试代码——CRC 通过。

SD 卡排除。

2.4 是不是内存布局冲突?

固件目标地址是 0xC0001000(SDRAM 起始偏移 4KB),Heap 在内部 SRAM,不存在重叠。

至此,常规怀疑对象全军覆没。问题一定出在某个外设在偷偷往 SDRAM 写数据。


三、锁定凶手:ETH MAC

3.1 谁有资格写 SDRAM?

在 STM32H7 的 AHB 总线矩阵上,能当 AHB Master 发起写事务的模块:

AHB Master 功能
Cortex-M7 CPU
DMA1 / DMA2 通用 DMA
MDMA 多主机 DMA
ETH MAC 以太网 DMA 引擎
SDMMC2 IDMA SD 卡内部 DMA
USB OTG USB DMA

SDMMC2 用 IDMA 把固件从 SD 卡搬到 SDRAM——这步是正常的,CRC 在 IDMA 传输完成后才做。所以问题一定发生在"固件加载完成之后"到"CRC 校验执行之前"这短短的时间窗口里。

3.2 对比实验

直接把整个 ETH 初始化函数砍掉(什么也不干直接返回)——CRC 通过。

问题锁定在 ETH 初始化过程中。

3.3 逐步缩小——二分注释法

ETH 初始化的步骤大概是这样:

ETH 初始化()
  ├── 配置 GPIO
  ├── 配置 MDIO (PHY 管理接口)
  ├── ETH MAC 复位 (AHB reset + DMAMR_SWR)
  ├── PHY 初始化 (自协商等)
  ├── MAC/DMA 配置
  └── 注册组播 MAC 地址  ← 这里会写 MACHT + MACPFR 两个寄存器

实验记录:

实验 做 法 结 果
1 ETH 初始化直接返回
2 GPIO 配完就返回
3 MAC 复位做完就返回
4 注册组播 MAC 地址直接 return
5 只写 MACHT,不写 MACPFR
6 只写 MACPFR,不写 MACHT
7 两个都写(原始逻辑)
8 两个都写,但中间隔 10ms
9 两个都写,中间加 __DSB()

3.4 铁证

  • 单独写任何一个寄存器 → 安全
  • 连续写两个 → SDRAM 被破坏
  • 中间加延时 → 安全
  • 加 CPU barrier (__DSB()) → 无效

__DSB() 无效说明问题不在 ARM 内核侧。是 MAC 外设内部的状态机出了岔子。


四、硬件层面的根因

4.1 关键硬件拓扑

                    AHB Bus Matrix (64-bit)
                         │
          ┌──────────────┼──────────────┐
          │              │              │
     Cortex-M7      ETH MAC          FMC
     (Master)       (Slave+Master)   (Slave)
                         │              │
                   ETH DMA Engine    SDRAM
                   (AHB Master)    (外部芯片)

ETH MAC 是一个双角色 AHB 设备:

  • AHB Slave:接收 CPU 对 CSR 寄存器(MACHT、MACPFR 等)的读写
  • AHB Master:DMA 引擎可以主动发起 AHB 读写事务,把网络数据包搬到系统内存

4.2 推测的故障机制

正常情况:
  CPU 写 MACHT  → MAC Slave 接口接收 → 更新内部寄存器 ✓
  CPU 写 MACPFR → MAC Slave 接口接收 → 更新内部寄存器 ✓

ETH MAC 刚复位后(异常窗口):
  CPU 写 MACHT  → MAC Slave 接收,但内部状态机部分误判为"DMA 描述符就绪"
  CPU 写 MACPFR → MAC Slave 接收,但内部误判为"启动 DMA 传输"
                 → DMA 引擎用残留的内部寄存器值做地址,发起一次 AHB 写事务
                 → AHB 矩阵把写事务路由到 FMC
                 → FMC 把数据写到外部 SDRAM
                 → 你的数据被静默覆盖

这也能解释为什么 corrupted CRC 是固定的

  • 虚假写事务的目标地址由 MAC 内部残留寄存器决定
  • 写出去的数据也由残留状态决定
  • 每次复位后残留状态相同 → 每次破坏的位置和内容都一样

4.3 验算:固件 CRC 的异常值

固件放在 0xC0001000(SDRAM 偏移 4KB 处)。如果 ETH MAC 的 DMA 引擎发出了一个地址在 FMC SDRAM 区域内的写事务,这笔数据就会直接覆盖固件镜像中的某个位置。

CRC32 是位置敏感的——只要有一个字节变了,整个 32-bit CRC 就会完全不同。0xD28F81A5 这个值就是固件镜像中被篡改后的 CRC。

4.4 ST 官方 Errata 有没有覆盖?

翻了 ES0491(STM32H72xx/73xx Errata, Rev 9, 2024-06-17)里全部 11 个 ETH 相关 errata(2.24.1 ~ 2.24.11),没有一条描述这个现象。

现有的 ETH errata 覆盖的是 DMA 仲裁优先级 bug、Bus error 恢复 bug、过滤/offload 逻辑 bug 等——没有一个提到"CSR 写触发虚假 AHB 事务"。

这是一个未被 ST 文档化的硬件 errata。


五、修复方案

推荐修复

在 ETH MAC 复位完成后加 10ms 延时,让 MAC 内部 AHB Master 状态机稳定下来:

void eth_mac_reset(void)
{
    /* AHB 总线复位 */
    RCC->AHB1RSTR |=  RCC_AHB1RSTR_ETH1MACRST;
    RCC->AHB1RSTR &= ~RCC_AHB1RSTR_ETH1MACRST;

    /* DMAMR 软件复位 */
    ETH->DMAMR |= ETH_DMAMR_SWR;
    while (ETH->DMAMR & ETH_DMAMR_SWR);  // 等待复位完成

    /* 配置 DMAMR、DMASBMR 等寄存器 */
    ETH->DMAMR   = (ETH->DMAMR   & ~DMAMR_CLEAR_MASK)   | DMAMR_SET_MASK;
    ETH->DMASBMR = (ETH->DMASBMR & ~DMASBMR_CLEAR_MASK) | DMASBMR_SET_MASK;

    /*
     * Workaround for undocumented STM32H723 ETH errata:
     *
     * After AHB reset + DMAMR_SWR, the MAC AHB master interface is not yet
     * fully stable. Consecutive CSR writes (e.g. MACHT followed by MACPFR)
     * can be misinterpreted by the internal DMA control logic, triggering a
     * spurious AHB write transaction that gets routed through the bus matrix
     * to FMC, silently corrupting external SDRAM.
     *
     * A delay here allows the internal state machine to stabilize.
     * 10ms is sufficient; 1ms was also verified to work.
     */
    delay_ms(10);
}

备选方案

  • 调整初始化顺序:把 ETH 初始化放到所有 SDRAM 关键数据加载完成之后。
  • 连续 CSR 写之间插入读回write_reg(MACHT, val); read_reg(MACHT); write_reg(MACPFR, val);

不建议的做法

  • 依赖 __DSB() / __DMB() —— 这是 CPU 侧的 barrier,管不到 MAC 外设内部状态机。
  • 单纯注释掉某个 CSR 写 —— 会破坏 MAC 功能(比如组播过滤失效)。

六、排查经验

6.1 方法论

  1. 先判断故障的"确定性":固定 CRC 值 = 确定性 bug,不是随机电气干扰。这是最重要的第一判断。
  2. 了解总线拓扑:知道哪些外设有 AHB Master 能力,才能知道谁有资格"偷偷写内存"。
  3. 对照实验是王道:每次只改一个变量(单写 MACHT vs 单写 MACPFR vs 都写 vs 都写+delay),结论才可靠。
  4. 先查官方 Errata:虽然这次是未公开的 bug,但先查一遍避免白费力气。
  5. 信任实验数据,不信任假设:中断洪水、DCache、栈溢出——都是合理的怀疑方向,但实验数据才是最终裁判。

6.2 教训

  • 外设 DMA 引擎也是 AHB Master,它可以在 CPU 完全不知情的情况下读写任意 AHB 地址。MPU 只保护 CPU 侧访问,管不到外设 Master。
  • “复位完成"不等于"状态机完全稳定”。数据手册里说的"复位后即可操作"可能只是指寄存器接口就绪,内部跨时钟域的状态机同步可能还需要额外时间。
  • CSR 写不是无副作用的。你以为只是在写一个配置寄存器,实际上可能在 MAC 内部触发了你完全意想不到的状态迁移。

七、附录:复现最小代码

// 不依赖任何 OS,纯寄存器操作即可复现

// 1. 使能 ETH MAC 时钟
RCC->AHB1ENR |= RCC_AHB1ENR_ETH1MACEN;

// 2. ETH MAC 复位
RCC->AHB1RSTR |=  RCC_AHB1RSTR_ETH1MACRST;
RCC->AHB1RSTR &= ~RCC_AHB1RSTR_ETH1MACRST;
ETH->DMAMR |= ETH_DMAMR_SWR;
while (ETH->DMAMR & ETH_DMAMR_SWR);

// 3. 往 SDRAM 写一些测试数据
uint32_t *sdram = (uint32_t *)0xC0001000;
sdram[0] = 0xDEADBEEF;
sdram[1] = 0xCAFEBABE;

// 4. 连续两次 CSR 写(不延时)
uint32_t tmp;
tmp  = ETH->MACHT0R;
tmp |= (1 << 5);
ETH->MACHT0R = tmp;                    // 第 1 次写

tmp  = ETH->MACPFR;
tmp |= (ETH_MACPFR_HMC | ETH_MACPFR_HPF);
ETH->MACPFR = tmp;                     // 第 2 次写 ← 可能破坏 SDRAM

// 5. 校验 SDRAM 数据
if (sdram[0] != 0xDEADBEEF || sdram[1] != 0xCAFEBABE) {
    // SDRAM 数据被破坏了!
    while (1);
}

相关寄存器地址

寄存器 地址 说明
ETH_MACHT0R 0x40028100 Hash Table Register 0
ETH_MACHT1R 0x40028104 Hash Table Register 1
ETH_MACPFR 0x40028108 Packet Filter Register
ETH_DMAMR 0x40028000 DMA Mode Register (SWR = bit 0)

后记

这个问题前前后后花了两天,从"CRC 不对"追到"写个寄存器 SDRAM 就被踩",跨了软件、驱动、总线矩阵、外设内部状态机四个层次。

目前已经整理成英文技术报告准备提交给 ST FAE。如果 ST 确认并给出了官方 Errata 编号,我会更新这篇文章。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐