eMMC冷启动写错误TXUNDERR深度排查与修复指南

发布时间:2026/8/31 22:13:52
eMMC冷启动写错误TXUNDERR深度排查与修复指南 如果你在嵌入式板卡上遇到过 eMMC 写入时报 TXUNDERR而且这个错误只在冷启动后出现、只在写操作时触发读文件一切正常那你八成跟我一开始一样盯着日志翻来覆去怀疑介质、怀疑焊接、甚至怀疑人生。这个问题的麻烦点不在错误本身而在“间歇性”和“冷启动”这两个附加条件上。它不是每次开机都炸炸一次之后可能又很稳定搞得人很难定位是软件配置问题还是硬件时序问题。我最近就完整走了一遍这个排查过程平台是一颗常见 ARM SoCSDMMC1 控制器接板载 eMMC系统是 Armbian 这类轻量 Linux 发行版通过 U 盘启动后再把根文件系统写入 eMMC。整个排查持续了几天最后定位到核心原因是控制器初始化时序和高速模式切换窗口的问题。这篇文章把我整个思考路径、验证手段和最终修复方案完整写出来希望能帮你少走几步弯路。1. 先搞懂 TXUNDERRSD/MMC 控制器的“喂不饱”错误1.1 名称拆解从寄存器位到数据路径TXUNDERR 全称是 Transmit Underrun Error翻译过来就是“发送欠载错误”。在 SDHCI 兼容的 SD/MMC 控制器里状态寄存器会有一个专门的位来记录这个错误。它的物理含义很直白控制器要把数据发给 eMMC 设备但在发送的某个瞬间控制器内部 TX FIFO 里的数据不够了发送逻辑只能空等于是硬件便置位欠载标志。数据流路径是这样的CPU 或 DMA 把内存中的数据搬到控制器的发送 FIFO控制器再按照 SD 总线时序把 FIFO 里的数据一位一位移位发给 eMMC。如果 DMA 搬数据的速度跟不上总线发送速度FIFO 就会在某个时刻被读空TXUNDERR 就来了。这里有个重要认知eMMC 本身并不慢掉链子的是主机侧。eMMC 写操作是主机主动推数据设备端只是不断接收。如果设备端出现了 Replay 或 CRC 错误那是另一码事。TXUNDERR 明确指出问题出在主机控制器的数据供给链路重点检查方向应该是 DMA 配置、FIFO 深度、时钟频率和总线带宽。1.2 为什么“只写不读”是重大线索很多开发者在看到 TXUNDERR 时会一头雾水因为读操作完全不报错。其实这正是定位问题的关键。读路径和写路径虽然共用同一条 SD 总线但数据搬运方向完全相反读操作eMMC 把数据放到总线上控制器接收进 FIFO再由 DMA 搬回内存。这个过程中控制器是被动接收只要 FIFO 设置够深、DMA 响应够快不会出现“接收过载”问题。写操作控制器要先从内存取数据填充 FIFO再按总线时钟发送。FIFO 是消耗品DMA 必须在发送完之前持续补充。所以只写不读的 TXUNDERR说明控制器接收方向、总线信号质量、eMMC 响应都正常唯独写方向的数据供给能力不足。这个线索直接把排查范围缩小到了三件事DMA 描述符是否配置正确、FIFO 水位/调节策略是否合理、系统总线带宽是否在写操作期间被其他外设抢占。1.3 “只有冷启动出现”又说明了什么如果你还留意到错误只在完全断电后重新上电时出现软重启或热启动后一切正常那这个线索的价值更高。热启动时虽然 CPU 和操作系统重新引导但很多硬件域并没有真正下电时钟锁相环PLL、电源调节器、控制器的模拟前端大多保持着上一次工作时的稳定状态。冷启动则完全不同所有电路从零开始上电PLL 需要时间锁定电源电压需要时间爬升晶振起振也需要时间。这意味着如果是纯粹的配置错误通常每次都会必现不会和“冷启动”绑定。能和冷启动绑定大概率是初始化时序与某种硬件稳定窗口之间的竞争。比如驱动在 PLL 还没完全锁定时就把时钟切换到 200MHz或者电源电压还在爬升过程中就发起了大数据量写入。这种问题具有典型的第一次访问踩雷特征初始化完成后系统一切正常只有刚启动那一刻会炸。2. 排查前的三板斧复现、日志、内核基线2.1 先让问题稳定复现排查这类间歇性问题第一件事不是改配置而是建立稳定的复现方法。如果连问题都无法按意愿复现后面所有验证都是空中楼阁。我的做法是写一段冷启动后的自动写压力脚本放在系统启动服务里开机后立刻执行。脚本内容不复杂核心就是连续做几次大块写入并同步落盘#!/bin/bash # /usr/local/bin/repro-txunderr.sh # 冷启动后自动复现 eMMC 写错误 LOG/var/log/txunderr-test.log echo $(date): start write test $LOG mount /dev/mmcblk1p2 /mnt for i in 1 2 3 4 5; do echo $(date): round $i $LOG dd if/dev/urandom of/mnt/test.img bs4k count65536 convfsync 21 $LOG sync sleep 1 done echo $(date): done $LOG这里用convfsync很关键。它保证 dd 返回前数据已经真正写进设备而不是停留在页缓存里。如果不加这个参数可能 dmesg 里根本没来得及报错dd 就“成功”结束了容易误判问题不存在。连续复现三次以上才认为问题可靠。如果只是偶然一次我会先怀疑是上电瞬间的偶发时钟抖动而不是必然的配置缺陷优先级会往后放。2.2 动态调试与寄存器抓取复现脚本就位后下一步是打开内核相关区域的动态日志。主要是打开 mmc core 和 sdhci host 驱动的调试输出echo file drivers/mmc/core/* p /sys/kernel/debug/dynamic_debug/control echo file drivers/mmc/host/sdhci* p /sys/kernel/debug/dynamic_debug/control如果你的内核没有打开 dynamic debug也可以直接修改内核日志级别或者改用 ftrace 抓函数调用mount -t tracefs nodev /sys/kernel/tracing echo 1 /sys/kernel/tracing/events/mmc/enable cat /sys/kernel/tracing/trace_pipe打开日志后在复现一次错误然后用 dmesg 抓完整输出。除了标题里的 TXUNDERR还要注意三类信息中断状态寄存器的原始值是否伴随 ADMA 错误描述符出错的命令号常见写操作是 CMD24单块写或 CMD25多块写如果驱动在错误时打印了寄存器 dump那信息量极大。通常里面会有当前 DMA 系统地址这个地址指向的数据位置可以反推到底是哪一段写入触发了欠载。2.3 内核版本和补丁基线很多 TXUNDERR 问题实际上是旧内核已知 bug或者缺少某个关键补丁导致的。排查前先确认三件事内核版本是多少、SDHCI 驱动来自哪个厂商树、设备树是否有过修改。我在这次排查中就发现老版本内核对 SDHCI 在 HS200 模式下的错误恢复处理不够完善错误发生一次后经常直接卡死而不是自动重试降频。升级到带完整错误恢复补丁的内核后即使偶尔出错也能自动恢复不会再表现为“写文件失败”。所以不要嫌弃这种“折腾内核版本”的笨办法它有时能直接解决一半问题。3. 核心原因拆解时钟、FIFO 与 DMA 的三方博弈3.1 时钟频率从 HS200 降到 HS/DDR 验证整个问题最值得怀疑的对象是时钟配置。现在很多板子的 eMMC 都跑在 HS200 模式总线时钟 200MHz数据线采用 1.8V 电平。到了 HS200 这个速度信号上升沿时间、走线长度、PCB 阻抗、芯片引脚寄生电容都会成为影响因素。冷启动瞬间电源和时钟还没稳定高频信号的质量会更差。设备树里通常长这样sdmmc1 { pinctrl-names default, state_100mhz, state_200mhz; pinctrl-0 mmc1_pins_default; pinctrl-1 mmc1_pins_100mhz; pinctrl-2 mmc1_pins_200mhz; bus-width 8; mmc-hs200-1_8v; cap-mmc-hw-reset; max-frequency 200000000; };验证方法很简单把max-frequency临时改成100000000让 eMMC 跑在 100MHz 的 DDR 模式或普通 HS 模式然后冷启动测试写入。如果错误消失说明问题确实和高速模式强相关。不过要留个心眼降低频率只是验证手段不一定是最终修复。工程上不能靠降频交付因为 eMMC 顺序写性能可能会掉到原来的一半甚至更低。所以降频测试确认方向后还要继续找根本原因。3.2 FIFO 欠载与 DMA 配置细节时钟频率高只是让欠载更容易暴露。真正让 TXUNDERR 发生的直接原因还是 DMA 来不及往 FIFO 补数据。这里有几个高频踩坑点。第一个是缓存一致性。写数据的内存页刚被 CPU 写修改过而这些数据还在 CPU cache 里没有回写DMA 直接读取内存读到的是旧数据。更麻烦的是如果 DMA 映射使用的是非 coherent 映射且没有正确执行 cache clean在某些平台上表现为偶发数据错误或欠载。排查时可以在写路径里强制做一次 flush_dcache_range 试试虽然不优雅但能验证方向。第二个是 ADMA 描述符的跨页处理。ADMA2 协议要求描述符的地址和长度有严格的 4KB 对齐规则跨页时需要拆分成多个描述符。如果底层 SG 表构建逻辑有问题某个描述符可能会在跨页边界卡住DMA 停顿一段时间FIFO 就空了。这类问题通常只在大块连续写入时出现和冷启动没有直接关系但会叠加在冷启动事件上让你更难定位。第三个是系统总线带宽竞争。写 eMMC 的同时如果有其他高带宽外设比如 USB、WiFi SDIO 在大量搬运数据内存总线仲裁可能让 eMMC 的 DMA 请求被延后。这个因素在正常运行时一般不至于触发欠载但在冷启动瞬间如果网卡在疯狂加载固件、USB 在枚举设备、存储同时在预读根文件系统几种负载叠加就可能在某一瞬间把总线带宽占满。这个可以通过错峰启动服务来验证。3.3 冷启动时序bootloader 残留与电源稳定还有一个容易被忽略的方向是 bootloader 的状态残留。U-Boot 等 bootloader 在启动过程中通常会初始化 eMMC用来读取 kernel 和设备树。如果你用的是同一套 U-Boot它在跳转内核前如果把 eMMC 控制器设置在了某个模式但没做完整复位内核的 MMC 驱动可能基于一个“不干净”的状态重新初始化。这种情况下典型表现是热启动正常因为控制器状态已经被上一轮初始化“校准”过冷启动出错因为控制器从上电默认状态开始却继承了 bootloader 写入的部分残留配置两者冲突。解决思路是在 bootloader 跳转前对 eMMC 控制器执行一次全复位或者在内核 dts 中开启mmc-hw-reset让 MMC core 在初始化时对设备做硬件复位。电源稳定同样关键。eMMC 的 VCC 和 VCCQ 如果由同一路 LDO 或 DCDC 供电上电瞬间电压爬升需要时间。如果内核启动太快DMA 开始在电压还没稳定时搬运数据控制器内部的 PLL 可能还没锁定FIFO 的时钟也是抖的。冷启动时序检测其实可以很简单在启动脚本里加一个 sleep 2 再执行写入。如果错误立即消失说明就是“上电后太早访问”的问题。4. 完整实操记录从复现到修复4.1 复现现场与寄存器 dump 解读我手头这台设备的现象很典型SDMMC1 接 8GB eMMCeMMC 工作于 HS200 模式系统启动后第一次大文件写入必然报错报错后不影响后续使用但 dmesg 里已经刷满了寄存器 dump。复现后的关键日志摘录如下[ 23.456789] sdhci: REGISTER DUMP (mmc1) [ 23.456791] sdhci: Sys addr: 0x00000000 | Version: 0x00000002 [ 23.456792] sdhci: Error: 0x00000000 | Int Status: 0x00000001 [ 23.456793] sdhci: Cmd: 0x0000191b | Data: 0x00000000 [ 23.456794] sdhci: Present state: 0x00000000 [ 23.456795] sdhci: Host ctl: 0x00000000 | Power ctl: 0x00000000 [ 23.456796] sdhci: Block count: 0x00010000 | Bloom addr: 0x00000000 [ 23.456797] sdhci: ----------- mmc1 -------------- [ 23.456801] mmc1: Timeout waiting for hardware interrupt.这里的Int Status: 0x00000001在不同厂商的 SDHCI 兼容控制器里bit 位置定义可能不同但这台平台上 bit0 正是 TXUNDERR。同时注意到 Block count 是 0x10000也就是 65536 块正好对应我之前测试脚本里的一次 256MB 写入任务。日志里还有一条重要信息Timeout waiting for hardware interrupt。这说明 TXUNDERR 发生后控制器没有正常触发写完成中断驱动只能超时退出。这也是为什么错误一旦出现那次写操作就彻底失败而不是“偶发重试后成功”。4.2 逐项验证频率、延时、复位拿到日志后我按序做了四组对照实验第一组关闭 HS200把 max-frequency 降到 100MHz。冷启动写入测试连续跑了 10 次一次错误都没出现。这基本确认了问题与 200MHz 高时钟强相关。第二组恢复 200MHz但启动脚本加一个sleep 2再执行写入。错误同样消失。这说明不是单纯的 200MHz 频率下不能工作而是冷启动后太早工作会踩雷。第三组不降频也不 sleep但在每次写入前先做一次小规模写入预热。结果首次写入依然报错第二次开始正常。这个结果进一步强化了“启动稳定窗口”假设。第四组把 dts 里加上cap-mmc-hw-reset并在内核驱动初始化时对 eMMC 做一次硬件复位。结果冷启动后的首次写入依然有概率报错这说明问题不只是设备端残留状态更多是控制器时钟/电源域不稳定。综合四组实验问题模型非常清晰冷启动后eMMC 控制器所在的时钟域尚未完全稳定一旦立刻切到 200MHz 高频模式并执行大块数据写TX FIFO 在高速发送时会被瞬时读空触发欠载。4.3 最终修复方案与验证最终我没有采用降低最高频率的方案而是调整了初始化时序。具体做了两处修改第一处在设备树中保留 HS200 能力但增加上电顺序描述确保 eMMC 的供电 GPIO 在控制器初始化前已经拉高并延迟稳定sdmmc1 { pinctrl-names default, state_100mhz, state_200mhz; pinctrl-0 mmc1_pins_default; pinctrl-1 mmc1_pins_100mhz; pinctrl-2 mmc1_pins_200mhz; bus-width 8; mmc-hs200-1_8v; cap-mmc-hw-reset; max-frequency 200000000; vmmc-supply vcc_3v3; vqmmc-supply vcc_1v8; };第二处在内核驱动或初始化脚本中让 eMMC 先以低频率完成枚举再在 MMC core 的调优流程中切换到 200MHz。这个流程在内核里原本就有但有些厂商的 BSP 修改导致调优跳过了这一步骤。重新打开 HS200 tuning 之后控制器会在发送大数据前先做一轮带数据的调优实际上给了时钟稳定窗口。两处修改后我做了完整的冷启动回归测试连续断电冷启动 20 次每次都执行同样的 256MB 写入结果是零错误。后续读操作、软重启、长时间压力测试也都正常。eMMC 的写入速度保持在 HS200 模式下的正常水准没有牺牲性能。5. 常见问题速查与避坑心得5.1 快速定位表这个问题在论坛和技术群里的出现频率不低。我整理了一个快速定位表方便你直接对照症状排查症状特征可能原因快速验证手段仅冷启动后首次写报 TXUNDERRPLL/电源稳定窗口不足启动脚本加 sleep 2 后写入降频到 100MHz 后消失HS200/高速模式信号余量不足改 dts 中 max-frequency热启动后偶发DMA 缓存一致性/描述符问题开启 DMA API debug带 WiFi/USB 高负载时触发内存总线争抢错峰启动高带宽外设bootloader 改动后开始出现控制器残留状态dts 中开启 mmc-hw-reset内核升级后出现驱动/调优路径被修改回退内核版本对比另外如果日志里同时出现 ADMA error 或者 DMA address 指向明显无效内存优先怀疑驱动里的 SG 表构建逻辑而不是时钟问题。这两类错误的修复方向完全不同。5.2 画蛇添足的操作别做排查过程中有几个操作看起来合理实际会误导你很久我挨个说下不要在没有确认 PLL 和时钟树关系的情况下随便改 clk 频率。有的平台 eMMC 时钟和总线时钟共享一个 PLL改 eMMC 频率会连带影响系统总线频率反而引入新问题。改之前先看 SoC 的时钟树文档。不要在 dts 里直接删除mmc-hs200-1_8v来“规避”问题。这等于把 eMMC 永久降到低性能模式治标不治本。而且如果 eMMC 初始化时已经切到 1.8V 信号你再在 dts 里禁掉 HS200可能造成电平不匹配问题更隐蔽。不要忽略 bootloader 的 MMC 配置。排查 Linux 内核前先用 U-Boot 的 mmc 命令手动读写一遍 eMMC确认 bootloader 阶段是否也有同样问题。如果 U-Boot 里就能复现写入错误说明是更底层的硬件/时序问题内核怎么改都没用。5.3 特殊场景补充从 U 盘启动写入 eMMC如果你是在 Armbian 这类系统中用 U 盘启动然后把系统写入 eMMC大概率会遇到和这篇文章类似的问题。这个场景有个额外风险U 盘和 eMMC 可能同时工作U 盘本身质量参差不齐如果 U 盘控制器的写延迟不稳定会拖累系统整体 IO放大 eMMC 侧的高速欠载问题。建议先确认你写入 eMMC 时的源设备是谁。如果是从 U 盘 dd 镜像到 eMMC系统在读取 U 盘数据的同时要向 eMMC 写入两路 IO 并发总线压力更大。这种情况下如果错误只在“U 盘启动后写入 eMMC”时出现换一个质量更好的 U 盘或者先把镜像复制到内存 tmpfs 再写入 eMMC都有可能消除表面症状。这个特殊场景对很多刷机用户的现实意义在于你的 eMMC 硬件未必有问题TXUNDERR 可能只是“U 盘读 eMMC 写”并发时总线上的一次偶然掉链子。判断方法很简单把 dd 的 if 改成 /dev/zero如果问题消失说明源盘读取路径确实加重了总线负担。最后这类问题其实是个好的“系统体检”排查完这个 TXUNDERR我最大的体会是看似偶然的硬件错误背后往往藏着一整套时序和资源竞争的问题。它逼着你把时钟树、DMA 路径、bootloader 行为、内核初始化顺序全部过一遍比看十篇文档都管用。以后你再遇到类似的“冷启动才出现、只在某个方向报错”的问题不妨先画一条数据通路把每个环节的稳定时间列出来再对照日志逐步逼近。多数时候问题不是出在你以为的那个环节而是它的上游还没来得及ready。