1. 项目背景与问题现象还原
1.1 这是一个什么项目
STM32MP135DAF是ST推出的单核Cortex-A7处理器,主频最高1GHz,定位在入门级工业MPU。相比STM32MP151/153这些双核兄弟,MP135砍掉了Cortex-M4协处理器,但保留了完整的多媒体外设、千兆以太网、CAN-FD、LCD控制器等,性价比很高,我看中它主要是因为单核A7足够跑轻量级Linux业务,叠加ST长期供货承诺,做中小型边缘计算网关和工业HMI很合适。
我这次要做的,是基于这颗芯片设计一块定制核心板,板载eMMC作为唯一启动介质,系统镜像用Yocto构建,machine层完全从ST官方stm32mp1派生,做了大量裁剪和定制。项目整体不复杂,但在eMMC启动环节踩了一个很深很典型的坑:内核PANIC之后,CubeProgrammer彻底无法重连目标设备。
这个问题在ST社区里反复出现,但大部分回复都停留在“按住复位键重试”或者“检查USB线”这种浅层建议。实际上,这里的连不上不是线缆问题,而是启动链路的失联,需要用一套系统性的方法去恢复。这篇文章就把我的整个排查过程、恢复步骤和背后的原理完整记录下来。
1.2 现场现象:从PANIC到失联的完整过程
先说现象,方便各位对照是不是同款问题。我通过CubeProgrammer把Yocto镜像烧进了eMMC,包含FSBL(Trusted Firmware-A)、U-Boot、内核、设备树和rootfs。第一轮启动很顺,U-Boot起来之后把内核拉起来,但内核在挂载rootfs时直接崩了,串口打印了经典的内核PANIC:
[ 2.315846] ---[ end Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(179,2) ]--- [ 2.325531] ---[ end trace 0000000000000000 ]---看到这行日志,我的第一反应是rootfs分区格式或root=参数不对,这是常规排查路径,也没太担心。但等我拔掉串口、想通过USB重新用CubeProgrammer烧一版修正后的镜像时,发现工具一直报No STM32 device in DFU mode connected,重新插拔USB、换了线缆、换电脑端口,全部无效,目标板像是彻底死透了。
这里要特别强调一点:这个PANIC本身很好解决,难的是PANIC之后CubeProgrammer为什么不认设备。两块问题交织在一起,才是这个项目的真正挑战。
1.3 为什么会选择eMMC启动
简单交代一下方案背景。STM32MP135支持从SD卡、eMMC、NAND、NOR Flash等多种介质启动,但我最终选了eMMC,原因有三个:
- 容量与成本平衡:eMMC 8GB版本在工业温度范围内成本完全可以接受,且容量足够放双rootfs做A/B升级。
- 可靠性:eMMC内置坏块管理、擦写均衡,对比裸NAND,软件栈省掉了一大堆MTD处理逻辑。
- 启动速度:eMMC的随机读性能远超SD卡,Linux系统启动时间能控制在3秒以内。
ST官方评估板的默认启动配置是SD卡+EMMC交叉配合,SD卡启动用于开发调试、eMMC用于固化量产。我是直接把SD卡槽砍掉了,所有启动内容和rootfs全部塞进eMMC。这个做法对量产很友好,但给调试恢复带来的麻烦,就是这篇文章要展开的核心内容。
2. 为什么eMMC启动失败会让CubeProgrammer失联
2.1 先搞懂STM32MP13的启动链路
要理解“为什么连不上”,得先把STM32MP13的BootROM启动流程讲透。芯片上电后,运行在芯片内部ROM中的一段固化代码(BootROM)会先读取OTP(One-Time Programmable)区域中的配置,再根据BOOT引脚的当前电平状态,决定从哪个外设加载FSBL(First Stage Boot Loader)。
在STM32MP13系列上,FSBL就是Trusted Firmware-A(TF-A)。BootROM把TF-A从启动介质中读取到内部SRAM并跳转执行,TF-A随后初始化DDR,把U-Boot(SSBL,Second Stage Boot Loader)加载到DDR中运行,U-Boot再加载内核和设备树,最终挂载rootfs进入Linux。
这整条链路中,每一级都有独立的加载地址、数据格式和校验方式。任何一个环节异常,都不会继续往下走。特别注意:BootROM是所有启动方式的根,只要BootROM能运行、引脚配置没被锁死,芯片就永远有救。
2.2 Boot引脚与启动源的关系
STM32MP135有三根BOOT引脚:BOOT0、BOOT1、BOOT2。它们在上电复位时被采样,组合出不同的启动源。对于我这种去掉SD卡的板子,最关键的三种组合是:
| BOOT2 | BOOT1 | BOOT0 | 启动源 |
|---|---|---|---|
| 0 | 1 | 0 | eMMC(串行接口,SDMMC2) |
| 0 | 1 | 1 | eMMC(串行接口,SDMMC2)备用 |
| 0 | 0 | 1 | USB DFU(用于烧录和恢复) |
开发板通常用拨码开关或跳线帽切换这三个引脚,方便在“正常启动”和“USB烧录”之间切换。我在自己的核心板上用了0欧电阻选焊的方式,拨码开关太占空间,但调试期这么做其实有点激进——这个决定为后面CubeProgrammer无法重连埋下了伏笔。
2.3 CubeProgrammer的“救命通道”为什么断了
CubeProgrammer通过USB连接芯片的原理是这样的:目标板上电后,如果启动引脚配置为USB DFU模式,BootROM会枚举出一个DFU USB设备,此时CubeProgrammer通过USB DFU协议与BootROM通信,读写Flash、执行烧录。
这里最关键的认知是:BootROM就是最终兜底的恢复通道。CubeProgrammer能连上,本质就是BootROM在等你发指令。
那为什么会失联?答案是:BootROM根本没能进入USB DFU模式。我的板子引脚固定在eMMC启动模式,BootROM上电后永远先去eMMC读取TF-A,不会理会USB接口。正常情况下这没问题,因为eMMC里烧了好的固件,启动链路跑得通。但现在eMMC里的固件损坏了——更确切地说,eMMC中TF-A或U-Boot区域的数据本身并没有坏,而是后续环节出了问题,启动永远走不完。BootROM每次复位后都去eMMC读、执行、失败、再复位、再尝试,陷入一个死循环,根本轮不到USB DFU。
这不是芯片锁死了,也不是eMMC物理损坏了,纯粹是启动链条卡死在错误状态。明白这一点,恢复思路就清晰了:要么让BootROM去USB DFU模式,要么让eMMC里的固件恢复可启动状态。两条路,总得走一条。
3. PANIC根因排查:不是所有内核崩溃都一个样
3.1 先从PANIC日志说起
内核崩溃(Kernel Panic)是Linux内核遇到无法恢复的错误时主动停机的一种保护机制。串口那行VFS: Unable to mount root fs on unknown-block(179,2)信息量很大,我来拆解一下。
unknown-block(179,2)表示内核试图挂载一个它不认识的块设备。设备号机制里,主设备号179代表eMMC设备驱动,次设备号2代表/dev/mmcblk0p2。也就是说,内核知道有个eMMC,也识别出了分区2,但挂载失败。这种“认识但挂不上”的情况,最直接的原因通常有三个:
- rootfs分区本身没有格式化为内核支持的文件系统。
- 内核配置里缺少该文件系统的驱动(比如没编入ext4)。
- 设备树给eMMC的配置不对,导致分区表读取异常。
3.2 第一类根因:rootfs挂载失败
我在Yocto里用的是ext4格式的rootfs。检查内核配置时发现了一个典型的坑:用ST官方stm32mp1机器配置做基线时,内核默认把ext4编成了模块CONFIG_EXT4_FS=m。模块形式的文件系统驱动,在rootfs还没挂载时根本无法加载——鸡生蛋问题,内核需要从rootfs加载ext4模块去挂载rootfs。
这种问题在SD卡启动时一般不明显,因为ST的默认initramfs会处理模块加载。但我裁剪了initramfs,走的是直接挂载rootfs的路径,模块驱动就彻底失效了。
解决方法是把关键驱动直接编译进内核:
CONFIG_EXT4_FS=y CONFIG_EXT4_USE_FOR_EXT2=y改完配置重新编译内核,挂载问题就消失了。
3.3 第二类根因:设备树eMMC节点配置错误
我在做自定义机器配置时,基于官方stm32mp135f-dk.dts修改了自己的设备树。这中间有个容易出错的地方:eMMC和SD卡在STM32MP13上通常共用SDMMC2控制器,但eMMC需要额外打开8线模式。如果设备树里只配了4线SD模式,或者non-removable属性没设置,Linux的MMC子系统可能会把eMMC当成可移动设备处理,导致分区扫描异常。
我的设备树里相关部分最终是这样的:
&sdmmc2 { pinctrl-names = "default", "opendrain", "sleep"; pinctrl-0 = <&sdmmc2_b4_pins_a &sdmmc2_d47_pins_a>; pinctrl-1 = <&sdmmc2_b4_od_pins_a &sdmmc2_d47_pins_a>; pinctrl-2 = <&sdmmc2_b4_sleep_pins_a &sdmmc2_d47_sleep_pins_a>; non-removable; no-sd; no-sdio; st,neg-edge; bus-width = <8>; vmmc-supply = <&v3v3>; vqmmc-supply = <&vddflash>; status = "okay"; };关键属性逐个说:
non-removable:告诉内核这是焊死的设备,不是插拔卡,避免触发热插拔检测逻辑。no-sd和no-sdio:强制控制器走eMMC协议,避免启动时去探测SD和SDIO设备浪费事件。bus-width = <8>:eMMC跑8线高速模式,这是eMMC性能的关键。vqmmc-supply:配置信号电压域。eMMC在HS200/HS400等高速模式下需要1.8V信号电压,这个供电必须正确。
如果这些属性有遗漏,eMMC初始化可能不稳定,轻则速度跑不上去,重则分区读到一半失败,直接PANIC。
3.4 第三类根因:U-Boot环境变量与启动参数错误
U-Boot传递启动参数给内核时,bootargs环境变量是核心。我的Yocto机器配置里写过一段带默认值的环境变量,但U-Boot实际运行时,eMMC里的环境变量分区残留了旧值,覆盖了编译时写入的默认值。
现场查到的bootargs如下:
root=/dev/mmcblk0p2 rootwait rw看上去没问题,但问题出在U-Boot的mmc设备编号上。U-Boot里SDMMC1和SDMMC2的编号顺序、以及实际板子上eMMC挂在哪一路控制器,必须保持一致。如果U-Boot认为eMMC在mmc 1而内核设备树里对应sdmmc2,设备映射错位,内核就会用错误的root设备参数去挂载。
排查这类问题时,可以在U-Boot命令行手动确认:
mmc list输出的控制器列表应当与设备树一一对应。再看:
mmc dev 1 ls mmc 1:2如果能列出rootfs分区内容,说明U-Boot这边完全正常,问题只在bootargs传递环节。
4. 恢复流程:把半砖状态的板子救回来
4.1 先确认板子还活着
排查完内核PANIC原因,理论上我只需要重新编译、重新烧录就能解决。但CubeProgrammer连不上,所有常规烧录路径都断了。恢复操作开始前,务必先做三件事:
- 串口连接是否正常:串口能看到BootROM、TF-A或U-Boot的输出,说明芯片核心、电源、时钟都活着。
- 测量关键电源域:特别是eMMC的VCC和VCCQ电压。如果eMMC供电异常,BootROM读取永远失败,但不是芯片死了。
- USB线缆和接口排查:换主机、换线、直接插主板后置USB口,排除最基础的物理故障。
这里多说一句,我始终强调串口的重要性。很多工程师在开发阶段挂个串口只是为了看日志,但在恢复阶段,串口是判断板子生死的唯一标准。一个能输出字符的串口,意味着芯片在运行,问题一定有解。
4.2 强制进入Engineering模式
针对我的引脚固定问题,恢复思路是让BootROM绕过eMMC启动,强制进入USB DFU。STM32MP135的参考手册里有一种方式:在TF-A和U-Boot中启用Engineering Mode,运行时可以通过USB强制引导到DFU。
但这里有个逻辑陷阱:如果eMMC里的TF-A或U-Boot本身已经损坏到无法运行,Engineering Mode根本不会被执行。我的情况是TF-A和U-Boot能运行,坏的只是后续内核环节,所以理论可行,但我不想赌,还是走最粗暴但最可靠的路:改引脚电平。
如果板子的BOOT引脚有跳线或拨码,直接拨到USB DFU组合(我的板子上是BOOT2=0, BOOT1=0, BOOT0=1),上电后CubeProgrammer就能重新发现设备。如果没有物理开关,那就需要用电线短接PCB上的焊盘或割线改造,这取决于你的板子设计。我自己在做板子时预留了0欧电阻位置,焊下来、换一个方向的电阻,就能强制进入DFU模式。
操作顺序:
- 板子完全断电。
- 修改BOOT引脚组合为USB DFU模式。
- 连接USB线到目标板的USB-OTG口(注意,一定是连接到芯片USB-OTG外设的那个口,不是USB Host口)。
- 给板子上电,观察主机的设备管理器或
dmesg,应当出现未知设备或DFU设备。 - 打开CubeProgrammer,选择USB模式,点击连接。
等待CubeProgrammer界面上出现芯片信息,一切就恢复了。
4.3 清除eMMC中的误导痕迹
连上CubeProgrammer后,按捺住直接烧新镜像的冲动。首先要做的是把闪存里导致反复启动失败的遗留数据清理干净。
在CubeProgrammer的存储器显示界面里,eMMC会以扇区形式呈现。启动引导数据通常存放在eMMC的前几个MB区域,包含TF-A、U-Boot和环境变量分区。我的做法是先不用整片擦除,只把前4MB区域清零:
0x00000000 0x00400000 0x00这个操作会把eMMC的bootloader区域全部写入0x00。执行后,eMMC里不再有可启动的代码,BootROM读取eMMC会失败,但这不是坏事——失败之后BootROM会等待USB DFU指令,芯片进入纯粹的“烧录等待”状态,后续操作非常安全。
此时再做一次断电重启,确认CubeProgrammer依然能连接。如果能连接,说明芯片和Flash的物理链路完全健康,之前只是启动逻辑死锁。如果不能连接,再检查USB PHY和时钟配置,但概率极低。
4.4 恢复烧录并验证启动链路
清理干净后,把修正过的镜像完整烧录进去。我习惯用CubeProgrammer的ExternalLoader配合分区表文件操作。对于eMMC,不依赖外部loader,流程如下:
- 在CubeProgrammer中选择
eMMC存储介质。 - 加载分区表文件(通常是
flashlayout.tsv)。 - 按分区表顺序烧录FSBL、U-Boot、环境变量、内核、设备树和rootfs。
- 烧录完成后断电,把BOOT引脚恢复到eMMC启动模式。
- 重新上电,串口观察启动流程。
烧录时有个细节:STM32MP13的eMMC启动分为boot partitions和user area。TF-A和U-Boot必须烧录到eMMC的boot1分区或位于用户分区起始位置的FSBL区。具体使用哪种方式,取决于你在烧写工具中定义的分区表。如果TF-A烧错了区域,BootROM同样找不到可执行代码。用CubeProgrammer时,Binary类型为FIP_MMC的件默认会写入boot1分区,千万别把FIP文件当普通分区镜像烧到user area。
boot加载过程中应该完整看到:
NOTICE: BL2: v2.8-stm32mp1-r1 NOTICE: BL2: Built : 11:36:10, May 10 2024 NOTICE: BL2: Booting BL32 NOTICE: BL2: Booting BL33最终串口进入Linux登录提示符,整个恢复流程才算是真正走完。
5. 常见问题速查与避坑记录
5.1 问题速查表
我把整个排查过程中遇到的和预判同行会遇到的问题整理成一张表,方便快速对照:
| 现象 | 直接原因 | 恢复/规避方法 |
|---|---|---|
内核PANIC,VFS: Unable to mount root fs | rootfs文件系统驱动未编入内核、root参数错误 | 检查CONFIG_EXT4_FS=y、确认root=/dev/mmcblk0p2 |
内核PANIC,卡在Waiting for root device | eMMC控制器初始化失败、设备树节点错误 | 核对bus-width、non-removable、vqmmc |
| U-Boot能启动,但内核完全跑不起来 | 设备树与bootargs不匹配 | 串口在U-Boot下执行printenv bootargs确认参数 |
CubeProgrammer报No DFU device | 启动引脚不在USB DFU模式、BootROM死循环 | 强制BOOT引脚为DFU组合,清除eMMC前4MB |
| CubeProgrammer能连接但烧录失败 | eMMC分区表错误、FIP文件位置不对 | 核对flashlayout.tsv,确保FSBL写到boot1分区 |
| 板子断电重启后仍无法从eMMC启动 | eMMC初始化时序不稳、供电不足 | 检查eMMC的VCC上电时序,必要时加延时 |
5.2 几条用板子换来的经验
第一,开发板要保留启动介质切换能力。不管量产方案是什么,开发阶段的板子一定要留BOOT引脚切换手段。引脚物理上可以焊接选择,但设计上务必留有测试点或0欧电阻位置。我这次恢复之所以费劲,核心原因就是在引脚设计上太激进,没有给自己留后路。
第二,Yocto定制machine时,内核关键驱动尽量直接编入内核而非模块。根文件系统所在的存储介质驱动、文件系统驱动、块设备驱动,都应该是=y而不是=m。这个原则对所有嵌入式Linux项目都成立,不仅限于STM32MP1。
第三,任何一次eMMC烧录,都要先想好“烧坏了怎么还原”。在烧录之前,先用CubeProgrammer做一个整片eMMC的备份(Read功能),或者至少备份前16MB。别嫌麻烦,一块板子等救援的时间成本,远超备份操作花掉的几分钟。
6. 给同样在搞自定义machine的同行一些建议
6.1 自定义machine的底线检查
基于ST官方机器配置做定制时,有几个地方必须逐项确认,否则很容易出现我遇到的这类问题:
MACHINE配置中的PREFERRED_PROVIDER_virtual/kernel是否明确。- 内核配置片段(
*.cfg)中是否包含rootfs驱动、eMMC驱动、USB DFU相关配置。 - U-Boot配置中定义了哪些默认环境变量,
bootcmd是否适配eMMC启动。 - 设备树是否引用了正确的eMMC控制器节点,pinctrl引脚是否与硬件原理图一致。
Yocto的可复现性很强,但定制越深、坑越隐秘。我的做法是建立一份自己的checklist,每次修改machine配置,都从头到尾过一遍启动链路。
6.2 建议的调试工具链
除了标配的串口和CubeProgrammer,我强烈建议准备以下工具:
- 逻辑分析仪或示波器:排查eMMC时钟和CMD/DATA线时序问题时,这比瞎猜快得多。
- USB分析工具:对比正常板子和异常板子枚举DFU设备的差异性。
- 多通道供电模块:确认eMMC上电瞬间的电压跌落,很多诡异问题其实是供电不足。
- SD卡槽转接板:就算量产不用SD卡,调试期保留一个SD卡启动选项,能大幅降低恢复难度。
6.3 后续扩展思路
这块板子最终是要进入量产的,eMMC启动方案还需要完善几个方向:基于U-Boot的冗余启动设计,TF-A和U-Boot做双备份,这样即使一次升级失败,备份分区还能兜底;增加OTA升级机制,在rootfs中集成更新脚本,配合U-Boot的distro_bootcmd实现A/B分区切换;还有就是量产阶段的烧录工艺,用ST提供的OTA烧录工具或自制产测脚本替代手工CubeProgrammer烧录,效率和防错能力完全不同。
这些扩展方向的核心思路是一致的:把恢复能力前置到设计阶段,而不是等设备出问题后再想办法。开发者板子可以随便折腾,但产品不行。
回到这次的问题本身,内核PANIC只是表面现象,真正的教训在于启动链路的设计必须保留“逃生通道”。CubeProgrammer连不上不可怕,可怕的是没有预案。希望这篇记录能给正在做STM32MP1系列BSP定制和eMMC启动方案的朋友一些参考,少走一点弯路。