约 2 分钟 阅读 31

给 ZynqMP 的 R5 核写驱动,第一次访问 PS 的 I2C0 控制器,整台板子死了——不是 R5 死,是整板:A53 四核失联、串口冻结、ping 不应,连 JTAG DAP 都一起瘫掉。这篇记录从锁死到结案的完整取证过程。

现场

新固件推进去,R5 正常启动,自检一路 PASS——直到第一个 I2C 传输开始,R5 再没输出过一个字符。控制台、SSH、ICMP 全部失联。

第一反应是 R5 崩了拖累系统?不对。JTAG 读 DDR 里的共享内存环:R5 固件好好的,自检输出停在那条 I2C 用例上,没有 fault 报告。halt R5 和 A53——全部超时。JTAG 复位——目标选择直接挂死。连调试器的路都死了,只能物理断电。

取证

恢复后抓到决定性证据(锁死前一刻的系统状态):

  • ff020000.i2cruntime_status=suspendedpower/control=auto
  • 时钟树里 i2c0_ref enable_count=0——I2C0 的时钟被门控了;对照组 UART0 是 1。

然后做了一次"自杀式"复现:A53 上用 /dev/mem 直接读 I2C0 寄存器(绕开驱动)——整板再次锁死。机制坐实。

根因

一句话:Linux 内核的 cdns-i2c 驱动在运行时 PM 下把空闲的 I2C0 时钟关了;任何绕过驱动的直接访问(R5 固件、A53 /dev/mem)命中一个"没有时钟的 APB 从机",LPD APB 互连永不应答——访问它的 CPU 核死在一条 ldr 指令上,而互连锁死又拖死了整片 LPD 外设和 JTAG DAP。

两个推论值得记住:

  1. 软件超时救不了你。R5 卡在总线等待里,ldr 永不退休,超时判断代码永远执行不到。所有"先访问再说、错了超时兜底"的写法在这种故障面前全部失效——唯一有效的防护是"确保从机可被访问"。
  2. 内核自己的 i2cdetect/i2cget 反而是安全的:走驱动路径会先 runtime resume 把时钟打开。所以故障只在"裸机核直访"或"/dev/mem"这种绕开驱动的路径上触发——这也是它为什么潜伏期这么难被发现。

修复(三层,纵深防护)

措施
根治(Linux 侧)power/control=on,systemd 持久化(keep-i2c0-clock.service),时钟永不门控
驱动侧R5 的 I2C 驱动每次传输前重断言时钟使能位(CRL CLKACT)——防 Linux 中途再关
代码门禁驱动加"武装"标志:未武装时收发一律拒绝(-EPERM),boot 自动路径在代码层不可能碰到 I2C0;调试命令默认拦截该地址区,要加 force 才放行

板验:时钟常开后同一地址连读连过,i2csmoke 5/5(EEPROM 读写回环/页写/RTC/NAK 负路径),boot 自检全绿。

带走的经验

  • 时钟门控 × 绕过驱动的直访 = 致命组合。只要系统里有一个核/一条路径绕开 Linux 驱动直接摸硬件,就必须把对应外设的运行时 PM 钉死为常开。
  • 互连锁死级的故障,取证窗口只有"锁死前那一刻"——runtime_status 和时钟计数器是我们能抓到的最后现场。
  • 防护要分层:根治一层、驱动一层、代码门禁一层。任何一层失守都不至于再回到"物理断电"。

标签: none

添加新评论