ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

STM32MP257 eMMC启动触发IAC exception 128?从TF-M到FIP的排查指南

STM32MP257 eMMC启动触发IAC exception 128?从TF-M到FIP的排查指南 1. 问题现象DFU能烧录eMMC启动却成了“鬼打墙”如果你正在调STM32MP257F-EV1大概率也遇到过这种让人血压飙升的场景板子昨天还用USB DFU好好地烧录、启动、跑Linux今天想把系统固化到eMMC按正常流程烧完、拨码切到eMMC启动、上电——结果串口开始刷屏板子无限重启日志里反复出现一行刺眼的报错。我这边固化的启动日志就卡死在下面这个状态[TF-M] IAC exception 128 [TF-M] System reset due to exception然后就开始循环复位、起、再复位、再起。更抓狂的是这时候你把USB线插到板子的DFU/OTG口主机端完全枚举不到设备fastboot devices一直空着STM32CubeProgrammer也连不上。表面看好像“板子变砖了”但扒开日志看问题并没有那么玄乎。先说清楚这次故障的现场环境和操作路径方便你对号入座硬件平台STM32MP257F-EV1 官方评估板目标镜像基于 STM32CubeMP2 SDK 构建的 TF-M FIP含 OP-TEE U-Boot以及 Linux 内核镜像烧录方式STM32CubeProgrammer 通过 USB DFU 将镜像写入 eMMC复现路径DFU 模式下一切正常切到 eMMC 启动后复现最终表现无限重启 IAC exception 128 fastboot 不进如果你也卡在“DFU好用、eMMC起不来”这个分界点上这篇文章会把异常的含义、启动链路的差异、排查顺序和修复手段都拆开讲。我自己已经从这套流程里救回来三块MP2板子了按下面的思路走基本不用怀疑硬件。2. IAC exception 128 到底在说什么先搞懂异常机制再动手排查之前先把报错本身啃明白。IAC exception的全称是Instruction Access Constraint它的异常类型编码EC是0x80十进制就是128。这是Armv8-M架构Cortex-M33下定义的一类故障异常核心含义是CPU在取指令阶段访问了一个当前执行状态不允许访问的内存地址。对这里的关键词是“取指令阶段”。它和数据访问异常不同IAC管的是“CPU从某个地址去拉下一条要执行的指令”这个动作本身的合法性。在STM32MP257这种双A35加一个M33的异构架构里TF-M跑在Cortex-M33上负责安全启动、DDR初始化、外设隔离和跳转控制。启动早期M33会完成内存映射和安全属性配置然后把非安全侧的镜像U-Boot、OP-TEE等交给A35去跑。这一跳转过程中如果发生下面任何一种情况都会捅出IAC exceptionCPU处于非安全状态Non-secure却尝试从一个被标记为安全Secure的地址取指令取指地址落在没有被映射的地址区间比如DDR控制器还没初始化好取指地址所在的内存区域被配置为不可执行XNExecute Never跳转目标地址本身和实际链接地址不一致跑到了一段“什么都不是”的数据区。用大白话讲你手里拿着导航但导航里填的终点是错的或者去终点的路被交警封了CPU一脚油门踩下去直接撞墙。IAC Exception 128就是这堵墙撞出来的巨响。很多人在这一步就开始怀疑“eMMC坏了”“DDR虚焊”其实大可不必。DFU模式下能用、切eMMC才崩说明硬件大概率没事问题几乎都出在软件链上TF-M与FIP版本不匹配、镜像没有放到eMMC正确的启动位置、或者启动源选择与烧录内容对不上。还有一点要特别说明异常在TF-M早期就发生了所以你在串口看到的是TF-M打印的异常信息而不是U-Boot的日志。如果TF-M日志只打印到一半就复位也能进一步佐证问题出在M33安全侧而不是A35侧的Linux内核。这为后面的排查顺序定了方向。3. DFU到eMMC启动的切换链路为什么DFU正常eMMC就崩要理解“为什么DFU好用、eMMC就崩”得先把STM32MP257的启动链路和两种启动方式的差异拉通。3.1 STM32MP257F-EV1的完整启动链MP2系列和MP1在启动流程上有个显著区别MP1用的是TF-A BL2而MP2改用TF-MTrusted Firmware-M作为BL2跑在Cortex-M33上。完整链路如下ROM (BootROM) - TF-M (BL2, 运行在Cortex-M33) - FIP (包含 TF-A BL31 / OP-TEE / U-Boot) - Linux Kernel关键点在前两级BootROM从启动介质中加载TF-M镜像TF-M负责DDR初始化、FIP解析、镜像校验然后把非安全侧的U-Boot和OP-TEE加载进DDR最后释放A35执行。也就是说整个启动链里“第一个吃螃蟹的人”是TF-M它一旦加载失败或者跳转出错后面全白搭。3.2 DFU模式与eMMC启动的本质差异DFU模式下STM32CubeProgrammer通过USB把镜像直接下载到DDR里然后从RAM启动。你烧录的内容、地址、Flash布局都是CubeProgrammer在PC端算好并控制的TF-M在RAM里被加载后DDR等硬件已经被初始化完毕一切看起来“岁月静好”。但eMMC启动时情况完全不同BootROM会先读取eMMC的boot分区或user area把TF-M镜像加载到内部SRAMTF-M运行后要自己完成DDR初始化再去eMMC读取FIP包FIP包在eMMC上的物理偏移必须和TF-M编译时约定的偏移完全一致烧录到eMMC的TF-M版本、FIP版本必须匹配同一个SDK版本。任何一个环节错位都可能造成TF-M取出错误的镜像数据跳到非预期地址触发IAC exception 128。所以不要因为DFU模式下能跑就默认eMMC烧录没问题——这两种方式对布局、版本一致性的敏感程度完全不同。3.3 切换时最容易被忽略的三个卡点我在实际排查中总结出三个高频卡点基本覆盖了这类故障的九成原因TF-M没有更新或者被覆盖。很多团队的U-Boot和Linux是频繁更新的但TF-M一份固件用很久。如果FIP升级了、TF-M还是老的两边对DDR安全区域划分、FIP偏移的理解不一致启动时直接跑到安全属性冲突的地址。烧录位置和启动方式不匹配。STM32MP257的eMMC可以从boot partition 1、boot partition 2或者user area启动烧录时要根据flashlayout.tsv的分区定义把TF-M和FIP写到正确的位置。比如TF-M要烧到eMMC boot1分区FIP也在boot1的后续偏移如果误烧到user areaBootROM找不到TF-M或者TF-M找不到FIP同样表现为死循环。BOOT引脚/OTP配置和实际烧录介质冲突。EV1板上有BOOT拨码开关如果拨到了eMMC启动但eMMC里根本没有有效镜像BootROM会反复尝试加载失败表现就是不断复位。这里可以做一个快速验证把BOOT拨回DFU启动模式如果板子能正常进入DFU基本可以判断硬件没有损坏问题集中在eMMC内容或启动配置上。4. 系统化排查链路从日志定位到根因一步步来面多这类“无限重启”故障最忌讳的就是一遍遍重烧、反复试然后越试越乱。我总结了一套从日志到配置的排查顺序按这个顺序走通常两轮之内就能定位。4.1 第一步确认异常发生的具体阶段先把串口日志完整抓下来我建议用逻辑分析仪或者串口工具记录完整的上电日志不要只看最后一段。关键要确认日志里有没有BootROM的打印MP2的BootROM一般会有启动源信息TF-M有没有打印出来打印到了哪一行就停止是TF-M自己报的IAC还是U-Boot阶段报的异常。如果日志里TF-M打印完IAC exception 128就复位说明异常发生在TF-M跳转前后。如果TF-M根本没打印可能是TF-M镜像本身没加载出来或者BootROM根本没从eMMC读到数据——那就是启动源和镜像位置的问题。我之前遇到的情况是TF-M打印正常说明BootROM成功从eMMC读到了TF-MDDR也初始化了但在跳转U-Boot时崩了。这就把方向直接指向TF-M和FIP的匹配性。4.2 第二步核对TF-M与FIP的版本一致性这是我在多块板子上踩出来的经验MP2平台这类跳转失败第一嫌疑永远是TF-M版本和FIP版本不匹配。为什么因为U-Boot的链接地址、DDR安全区域的划分、FIP的加载地址都在TF-M的配置里。FIP中的U-Boot被加载到哪个DDR地址、以什么安全属性运行是由TF-M决定的。如果FIP是SDK v4.1构建的TF-M却是v4.0的两个版本对地址映射和安全属性的定义有差异跳转时CPU很可能尝试从一个被标记为Secure的地址以Non-secure状态取指IAC就来了。验证方法很简单# 在SDK目录下查看当前构建的版本 git log --oneline -1 # 查看TF-M和FIP的编译时间 ls -l build/tfm/bin/tfm_bl2.bin build/fip/fip.bin # 用STM32CubeProgrammer读取eMMC中实际烧录的TF-M内容 STM32_Programmer_CLI -c portUSB1 -r 0x00000000 0x20000 tfm_dump.bin然后把读取出来的tfm_dump.bin和SDK里编译出来的tfm_bl2.bin做对比用sha256sum算一下哈希不一致就说明eMMC里的TF-M不是你想烧的那一版。4.3 第三步核实eMMC分区布局与烧录内容STM32MP2的烧录依赖flashlayout.tsv文件它定义了每个镜像烧到eMMC的哪个分区、哪个偏移。我见过太多人直接把旧工程的tsv拿过来用结果新工程的FIP偏移变了都不知道。正确的检查方式有两个层面第一个层面工程编译时生成的flashlayout.tsv要和实际烧录时使用的完全一致。打开文件重点看TF-M和FIP两行的偏移# 重点关注两个关键行 tfm_bl2.bin boot1 0x00000000 fip.bin boot1 0x00004000如果TF-M烧到了boot1起始偏移但FIP被定义到boot2而你的启动配置恰好选了boot1TF-M在boot1后续偏移读FIP就会读到空数据或者旧数据跳转自然失败。第二个层面通过STM32CubeProgrammer的-r命令把eMMC boot1分区完整读出来和编译产物逐个对比偏移处的内容# 读取eMMC boot1前512KB STM32_Programmer_CLI -c portUSB1 -r 0x00000000 0x80000 emmc_boot1_dump.bin对照tsv里的偏移看看对应位置的镜像哈希是否正确。这一步能把“烧录位置不对”这个根因直接坐实或排除。4.4 第四步检查BOOT引脚与OTP fuse的启动源配置如果TF-M、FIP内容都正确还是无限重启就要怀疑启动源本身是否和烧录介质一致。STM32MP257F-EV1上BOOT拨码开关可以直接指定启动介质。DFU烧录完成后如果拨码还在DFU位置强行写好了eMMC但没切过去自然不会从eMMC启动反过来如果拨码在eMMC位置但OTP里fuse也已经烧成eMMC启动那么即使DFU拨码想临时覆盖也可能被OTP的优先级压住。快速验证方法把BOOT拨码全部拨到DFU模式看板子能不能稳定进入DFU。如果能说明板子的基本启动链路是通的。接着检查OTP区域STM32_Programmer_CLI -c portUSB1 -otp read在输出的OTP表里找BOOT相关字段确认没有写入强制eMMC启动的fuse。如果OTP里确实有内容并且指向eMMC而你又确实想把系统留在eMMC那问题就回到eMMC内部布局如果OTP指向其他介质或者为空BOOT引脚就是唯一的决策者拨码切到DFU后重烧即可。4.5 第五步检查U-Boot与fastboot的联动配置最后再看fastboot为什么起不来。很多人以为fastboot是“USB枚举层面”的问题但其实在这类故障里它只是结果系统在TF-M阶段就崩了根本没走到U-BootUSB gadget自然永远不会枚举主机端fastboot waiting for device会一直等下去。如果前面的启动链都正常了fastboot还进不去才需要检查U-Boot配置CONFIG_CMD_FASTBOOT有没有打开CONFIG_USB_FUNCTION_FASTBOOT有没有打开U-Boot设备树里USB OTG节点EV1上是DWC3有没有正确配置为peripheral模式是否设置了fastboot的启动命令进入方式比如按键触发或环境变量控制。这里有个容易忽略的点Windows下fastboot识别设备需要正确的USB驱动Linux下需要udev规则。你可以先把USB线插到板子确认系统有没有新增USB设备。如果板子U-Boot正常起来了USB枚举是能看到VID/PID的Linux下用lsusb就能看到一行类似STMicroelectronics的设备。看不到再往U-Boot配置里查。5. 修复与验证强制DFU重刷让板子回到正轨定位到根因之后修复本身并不复杂但每一步都要验证避免“烧完还是老样子”。5.1 先让板子进入强制DFU模式不管eMMC里是什么状态只要BootROM还活着就能用BOOT引脚强制进入DFU模式。具体操作断开板子电源USB线也拔掉把BOOT拨码开关拨到DFU/USB启动方式EV1板卡上一般有丝印标注按用户手册确认用USB线连接板子的DFU口通常是标着USB1或DFU的Type-C口到电脑上电。如果一切正常PC端会枚举出一个USB设备Linux下lsusb能看到STMicroelectronics相关的设备Windows下设备管理器会出现一个STM32 Bootloader设备。此时用STM32CubeProgrammer连接STM32_Programmer_CLI -c portUSB1能连接成功说明BootROM和USB链路都没有问题可以放心刷写。5.2 校验eMMC状态并重新烧录进入DFU后先做一次eMMC的状态检查特别是boot分区的写保护位# 检查eMMC相关信息 STM32_Programmer_CLI -c portUSB1 -mmc info如果eMMC的boot分区被打上了写保护直接烧录会失败或者“假成功”。确认写保护状态后需要先解除保护再烧录。STM32CubeProgrammer通常提供了命令行选项也可以直接在图形界面里操作。如果有报错提示写保护就要先执行解除写保护的操作。接下来重新烧录完整的Flash布局# 使用与当前SDK版本匹配的flashlayout文件烧录 STM32_Programmer_CLI -c portUSB1 -w flashlayout.tsv烧录过程中留意每一条Download记录的偏移和长度确认TF-M写入了boot1的起始地址FIP写入了正确的偏移。烧录完成后建议立刻用-r命令回读校验一遍不要直接切启动否则万一烧错了还得重新进DFU。5.3 切换启动源并验证启动链烧录校验完成后断电把BOOT拨码切到eMMC启动重新上电。此时串口应该能看到完整的启动日志TF-M: boot stage 2 TF-M: FIP loaded TF-A: BL31 OP-TEE: ... U-Boot: ... Linux: ...如果还是复现IAC exception 128先别急着重复烧写。回到第4章的排查链路重点检查TF-M/FIP版本和boo1分区布局。我遇到过一次烧了三遍都没解决的问题最后发现是工程里的tfm_bl2.bin和FIP不是同一次make的产物重新统一构建后一次就过了。5.4 恢复fastboot的验证方法启动链恢复正常后U-Boot起来fastboot自然就能用了。验证方法在U-Boot命令行下执行fastboot usb 0主机端执行fastboot devices能看到设备时执行fastboot getvar version确认通信正常。如果你在Linux主机上遇到fastboot waiting for device但USB设备能看到大概率是udev规则没配上。在/etc/udev/rules.d/下加一条规则把ST的USB VID加入白名单然后udevadm control --reload即可。Windows下则要确认设备管理器里是否识别成ADB Interface或Android Bootloader Interface没有的话需要手动指定驱动。6. 把“无限重启”转化为可复用的排查方法论这类故障解决完我突然意识到一个问题IAC exception 128这类报错对老手来说就是“版本不匹配”的代名词但对刚接触MP2平台的人来说光看报错根本无从下手。如果你把这个故障场景当成一个案例从里面抽出一套可复用的排查方法大概可以浓缩成下面这张表排查维度检查内容快速定位方法异常阶段TF-M日志打印到哪里串口完整日志镜像版本TF-M与FIP是否同一次构建sha256sum对比eMMC回读内容烧录布局flashlayout.tsv是否匹配实际烧录回读eMMC分区并逐偏移检查启动源BOOT引脚/OTP fuse与实际介质一致强制DFU验证 otp readU-Boot配置fastboot相关configmenuconfig检查 USB枚举状态这张表不仅适用于STM32MP257F-EV1也适用于任何带TF-M/TrustZone安全启动链的板级调试。核心思路是一个“从内到外、从软件到硬件”的排查顺序先确认异常发生在哪一层再检查镜像和布局最后才去动硬件。我个人的几条实操体会最后分享几个我踩过坑之后的习惯不一定写在哪份文档里但对板级调试很管用。第一尽量用同一个SDK版本统一构建所有镜像。TF-M、FIP、U-Boot、Linux全部在同一棵代码树下编译禁止混用不同版本SDK的产物。MP2平台的启动链是连锁反应一个版本错位就可能导致跳转失败而这些问题排查起来非常耗时。第二每次烧录前先看一次eMMC状态。特别是boot分区写保护位。STM32CubeProgrammer连接后花几秒钟看一下mmc info确认boot1、boot2都能正常访问再烧能省掉很多“烧录成功但起不来”的无效调试。第三回读校验是烧录后必做的一步。烧完不要急着切启动先花30秒把关键分区回读出来和编译产物做哈希对比。这个习惯让我少走了很多弯路因为烧录过程中出现“静默失败”的概率比想象高。DFU模式下偶尔会出现数据超时导致写入不完整但工具不一定报错回读校验是唯一可靠的兜底手段。第四BOOT引脚状态和OTP fuse一致性检查放在最后一步做。很多工程师一遇到启动失败就疯狂拨BOOT开关其实是反的。先把内容烧对、版本配好再检查启动源选择。顺序反了容易陷入“拨码烧录、烧录拨码”的死循环越调越乱。这组问题解决下来我对TF-M在MP2平台上的定位有了更深的体会——它不只是安全组件更是整个启动链的交通指挥中心。理解了这个角色再回头看IAC exception 128就不觉得它有多神秘了。无非是“指挥中心给CPU指了一条不该走的路”。顺着这个思路复盘你也能在十分钟内把这个故障从“鬼打墙”变成“一眼看穿”。
返回列表