U-Boot移植实战:fastboot刷机与EMMC启动链路解析
移植 u-boot-2020.07 到 iTOP-4412八fastboot boot from EMMC前七篇的串口、网络、SD 卡启动都打通之后剩下两块最硬的骨头就是本篇要处理的 fastboot 刷机通道和 EMMC 启动链路。fastboot 管的是“怎么把系统装进板子”EMMC 启动管的是“脱离 SD 卡之后系统怎么自己起来”两个功能合在一起这块 iTOP-4412 才算真正具备产品化价值而不是一块只能在调试台上跑 demo 的开发板。这篇文章我按自己的移植过程来写从 U-Boot 2020.07 的 fastboot 配置到 BL1/BL2 的 EMMC 布局再到最后的联调验证尽量把配置、步骤和踩坑原则都讲清楚。适合正在做 Exynos 4412 或类似平台移植的朋友也适合对 fastboot 协议和 EMMC 启动流程还不熟的嵌入式新人跟着做一遍很多概念会一下子串起来。1. 为什么这一篇要同时处理 fastboot 和 EMMC 启动1.1 这两件事其实是同一个目标的两端你可能觉得 fastboot 和 boot from EMMC 是两个独立功能但实际移植时它们是强绑定的。fastboot 的本质是“通过 USB 把镜像写进存储介质”而 iTOP-4412 的量产存储介质就是 EMMC所以 fastboot flash 的目标设备必须配好 EMMC 相关内容反过来EMMC 上烧完的 U-Boot 和内核能不能正常启动也需要在启动参数、分区偏移上和 fastboot 的分区定义保持一致。我的习惯是先把 fastboot 跑通再调 EMMC 启动。顺序不能反。如果先做 EMMC 启动每次改 bootcmd、改内核加载地址都要重新烧卡、重新拔插 SD 卡调试效率极低。fastboot 通了以后改完镜像直接一条命令刷进 EMMC比反复折腾 SD 卡省一个数量级的时间。所以这篇的实操逻辑是先给 U-Boot 装上 fastboot 刷机能力同时把 EMMC 分区规划好让 fastboot 能写、uboot 能读、kernel 能启动三件事在同一个分区布局下完成闭环。1.2 移植前先搞清楚 4412 的启动链路iTOP-4412 这颗 Exynos 4412 的启动链路是典型的 ARM 多级引导iROM - BL1(Signed BL1) - BL2(U-Boot) - KerneliROM 是芯片内部固化的一段代码上电后根据 OM 引脚或内部寄存器选择启动介质。如果选择 EMMC 启动iROM 会从 EMMC 的固定块位置读取 BL1 到 iRAM 中执行BL1 完成 DDR 初始化之后再从 EMMC 的另一个固定偏移读取 BL2也就是 U-Boot跳转进去执行U-Boot 之后按 bootcmd 环境变量的指示从 EMMC 读取内核和设备树最终进入系统。理解这条链路对移植特别重要因为很多人移植 U-Boot 时只在源码层面把 EMMC 驱动配好却不知道 4412 的 BL1 和 BL2 各自烧在哪个扇区。后面的章节里我会把每个关键烧写偏移和原因都拆开讲你先在脑子里存下“多级引导”和“固定偏移”这两个概念。2. fastboot 移植给 U-Boot 装上 USB 刷机通道2.1 U-Boot 2020.07 的 fastboot 框架U-Boot 2020.07 里 fastboot 已经是一个比较成熟的子系统。它分成两层命令层cmd/fastboot.c负责解析fastboot usb 0这种命令行入口传输层drivers/usb/gadget/f_fastboot.c负责实现 USB 上的 fastboot 协议包括设备的枚举、命令解析、镜像传输。移植时我建议先把这两层源码翻一遍不用细读但要搞明白它们的依赖关系。fastboot 在 USB 上跑底层必须有一个能工作的 USB device controller也就是 UDC。4412 上的 USB 2.0 OTG 控制器在 U-Boot 里是 dwc2 驱动所以依赖链是CONFIG_USB_GADGET - CONFIG_USB_GADGET_DWC2 - CONFIG_USB_FUNCTION_FASTBOOT - CONFIG_CMD_FASTBOOT这条依赖链每一项都不能少。现实中有不少人只开了 CONFIG_CMD_FASTBOOT编译出来执行fastboot命令直接报“controller not found”就是这个底层 UDC 没使能。搜索正确答案也不用去文档里翻直接在源码里 grepfastboot usb就能看到命令实现再顺着看它调用了哪些 UDC 接口。2.2 关键配置项与内存 buffer 选择我在默认 defconfig 基础上为 iTOP-4412 新增了这样一组配置CONFIG_CMD_FASTBOOTy CONFIG_USB_GADGETy CONFIG_USB_GADGET_DWC2y CONFIG_USB_FUNCTION_FASTBOOTy CONFIG_FASTBOOT_FLASHy CONFIG_FASTBOOT_BUF_ADDR0x40008000 CONFIG_FASTBOOT_BUF_SIZE0x02000000 CONFIG_FASTBOOT_FLASH_MMC_DEV0这里有两个参数是移植 4412 时特别容易踩坑的一个是CONFIG_FASTBOOT_BUF_ADDR一个是CONFIG_FASTBOOT_BUF_SIZE。CONFIG_FASTBOOT_BUF_ADDR是 U-Boot 接收 fastboot 传输数据的暂存区地址。4412 的 DRAM 从 0x40000000 开始U-Boot 自身镜像经过重定位后通常会跑到 0x43e00000 附近所以 buffer 放在 0x40008000 是安全的——它避开了低地址的 BootROM 映射区和 BL1 的一些临时数据结构又远低于重定位后的 U-Boot。大小 32MB 是个折中值刷一个 8MB 的 kernel 镜像绰绰有余未来刷更大的 boot 分区也不至于临时改配置。注意如果 U-Boot 是从 0x40000000 开始由 BL1 直接跳过来的那 buffer 地址尽量不要选在 0x40000000 这个起始地址上因为有些 BL1 会把自定义的 magic 信息放在内存开头fastboot 写入时容易把这块覆盖掉。还有一点EMMC 上的分区读写用的是块设备接口fastboot flash 命令最终会调用fb_mmc_flash_write把 buffer 里的数据按分区定义的偏移写到 EMMC。所以CONFIG_FASTBOOT_FLASHy必须开不开的话fastboot flash会提示“flash not supported”。2.3 分区定义与烧写流程fastboot 在 U-Boot 里的分区不是像 Android 的 fstab 一样存在文件里的而是通过环境变量fastboot_partitions定义。这个变量的格式在不同版本上有差异我用的 2020.07 支持的是fastboot_partitionsname:size[:offset[:type]]项与项之间用逗号分隔。看 iTOP-4412 的存储需求和 boot 链路的偏移要求我规划了一组分区分区名大小起始块(512B)用途bl21MB0u-boot 引导镜像所在区kernel8MB0x800内核 zImagedtb1MB0x1000设备树system128MB0x2000rootfsuserdata剩余0x80000数据区你可能会问为什么 kernel 不从块 0 开始放因为 4412 的 iROM 和 BL1 在启动时会固定读取 EMMC 头部若干个扇区这些区域必须留给 BL1 和 BL2。如果 kernel 覆盖了启动头部板子会直接卡死在 iROM 或 BL1 阶段。分区定义写成环境变量就是这样setenv fastboot_partitions bl2:1M:0x0:0,kernel:8M:0x800:0,dtb:1M:0x1000:0,system:128M:0x2000:0,userdata:-1M:0x80000:0 saveenv注意 userdata 大小写成了-1M这是 2020.07 支持的一种表示“剩余空间”的写法实际含义是“把从偏移 0x80000 开始到 EMMC 结尾的空间都定义为 userdata 分区”。分区定义好之后板子端进入 U-Boot 命令行执行fastboot usb 0主机端就能用fastboot devices看到设备。然后按常规流程刷镜像fastboot flash bl2 u-boot-bl2.bin fastboot flash kernel zImage fastboot flash dtb exynos4412.dtb fastboot flash system rootfs.ext4 fastboot reboot这一套流程打通之后U-Boot 的 fastboot 通道就算完工了。后面所有镜像更新都不需要再拔 SD 卡直接 USB 线一插就是刷机模式。3. EMMC 启动移植从 BL1 到 kernel 的最后一公里3.1 BL1 与 BL2 镜像处理fastboot 通道只是把镜像写进 EMMC 的手段真正决定板子能不能从 EMMC 起来的是镜像本身和它的摆放位置。这里必须把 BL1 和 BL2 的关系说清楚。BL1 是 iROM 的第一跳目标它由芯片厂商提供带有校验签名。iTOP-4412 这类开发板板厂一般会随 BSP 包放出 BL1 的 bin 文件注意这个文件不能自己瞎编也不能用其他平台的 BL1 替代因为 Exynos 4412 的 iROM 对 BL1 有格式校验替代品直接卡死在第一步。U-Boot 编译出来的产物是u-boot.bin但直接把它当 BL2 烧进 EMMC 大概率起不来。原因是 Exynos 的 BL1 在跳转前会读取 BL2 头部的一段特殊信息里面包括镜像大小和校验值这个头部需要用工具预先打进镜像里。U-Boot 的 mkimage 工具支持exynos格式处理方法mkimage -T exynos -d u-boot.bin u-boot-bl2.binu-boot-bl2.bin才是能烧进 EMMC 的最终 BL2 镜像。我最初移植时偷懒直接烧了没加头的u-boot.bin结果串口输出停在 BL1 的一行提示之后就再无反应浪费了半天时间排查。务实提示如果板厂的手册里给了专门的 BL2 打包脚本优先用板厂的脚本不用自己折腾 mkimage。不同批次板子的 BL1 对 BL2 头部的检查要求可能存在差异原厂脚本是验证过的最稳妥方案。3.2 bootcmd 与 bootargs 的实战写法BL2 也就是 U-Boot 起来之后接下来要加载 kernel。这一步的核心是bootcmd和bootargs两个环境变量。我的设计是让 U-Boot 从 EMMC 上按固定偏移把 kernel 和 dtb 读到 DDR然后用bootz启动。iTOP-4412 的 DDR 布局里我惯用的加载地址是kernel 加载地址0x40008000dtb 加载地址0x41000000为什么选 0x40008000因为它和 fastboot 的 buffer 起始地址保持一致逻辑上减少混乱为什么 dtb 放 0x41000000因为 zImage 解压后内核通常占据从加载地址开始的 16MB~32MB 空间0x41000000 避开了这个窗口。对应的 bootcmd 写成setenv bootcmd mmc dev 0; mmc read 0x40008000 0x800 0x4000; mmc read 0x41000000 0x1000 0x100; bootz 0x40008000 - 0x41000000拆开解释一下mmc dev 0选中 EMMC 设备mmc read 0x40008000 0x800 0x4000表示从块地址 0x800 开始读 0x4000 个扇区到 0x40008000这里 0x800 对应 fastboot 分区表里的 kernel 分区起始块0x4000 个扇区正好 8MBmmc read 0x41000000 0x1000 0x100同理从块 0x1000 读 0x100 个扇区也就是 512KB 的 dtbbootz 0x40008000 - 0x41000000中间的-表示没有 initramfs。bootargs 是传给内核的命令行4412 平台至少要包含 console、root、rootwait我常用的写法setenv bootargs consolettySAC2,115200 root/dev/mmcblk0p4 rw rootwait这里的ttySAC2是 4412 的串口设备节点名如果你的内核配置里用的不是这个节点以实际为准root/dev/mmcblk0p4指向的是 system 分区对应的内核块设备节点注意它和 fastboot_partitions 里的分区序号要对得上否则内核挂载 rootfs 时会报找不到设备。saveenv保存之后每次上电 U-Boot 都会直接执行这条 bootcmdEMMC 启动就算搭起主干框架了。3.3 烧写 EMMC 与验证启动烧写 EMMC 有两个入口。第一个入口是用 fastboot直接把 u-boot-bl2.bin、zImage、dtb 刷到对应分区第二个入口是先用 SD 卡启动 U-Boot再用mmc write命令手动往 EMMC 写。我验证时用的是第一个入口因为分区和地址都已在 fastboot 环境变量里定义好命令最短fastboot flash bl2 u-boot-bl2.bin fastboot flash kernel zImage fastboot flash dtb exynos4412.dtb fastboot flash system rootfs.ext4刷完先不要急着重启建议回到 U-Boot 命令行手动执行一次 bootcmd 确认加载逻辑没问题run bootcmd这一步能直接看到 EMMC 读取是否正常、内核是否启动。如果一切顺利reboot 后拔掉 SD 卡把拨码开关切到 EMMC 启动模式上电后串口应该依次出现 BL1、U-Boot、Kernel 的启动日志。我自己在实际验证时遇到过一种奇怪情况手动 run bootcmd 能启动但断电重启后卡死在 U-Boot 之前的 BL1 阶段。最后查下来不是 bootcmd 的问题而是 BL1 所在区域被我后面频繁刷写时覆盖了。这提醒我每次 fastboot flash bl2 之后必须重新确认 BL1 还完好不能默认烧过一次就永远可用。4. 联调手记一次完整的 fastboot 刷 EMMC 过程4.1 主机端与板端的准备联调前先确认两边环境都齐了。我是 Ubuntu 主机板子通过 USB OTG 线连到电脑OTG 口要选开发板上标注的 USB Device 口不是 HOST 口——这两个口在 4412 核心板引出的排针上很容易看错接错的话 fastboot 在板端根本枚举不出设备。主机端要有 fastboot 工具Android platform-tools 里自带。装完建议先测一下fastboot --version能输出版本号就行。有些发行版需要给 USB 设备写 udev 规则才能让 fastboot 识别非 Android 设备否则会一直提示 waiting for device。我遇到这个坑时临时用sudo fastboot devices绕过去了但正规做法是加一条匹配 USB Vendor ID 的规则让普通用户也能访问。板端先把 U-Boot 跑起来进命令行执行fastboot usb 0。此时板子会停在一个 fastboot 服务状态控制台能看到类似“Enter fastboot usb”的提示USB 枚举成功后主机的fastboot devices会列出一个设备。4.2 分区布局设计分区布局这块我在 2.3 节给过一张表这里再强调一次设计原则。4412 的 EMMC 头部不能随便放东西因为 iROM 固定读前几个扇区作为 BL1 区。我在实际项目里把 BL1 和 BL2 放在一个 1MB 的 bl2 分区里kernel 从偏移 1MB 处开始也就是块地址 0x800。这样设计有几个考虑1MB 的头部余量足够容纳 BL1通常 64KB和 BL2通常几百 KB不会被跨分区覆盖kernel 从 1MB 开始和很多 4412 原厂烧写工具的分区习惯一致后续如果换回原厂工具也不会互相冲突EMMC 的块擦除粒度是 512KB 级别分区起始地址对齐到 1MB 能减少写放大。分区大小也不需要太精确kernel 给 8MB、dtb 给 1MB 是保守值zImage 一般 4MB 左右1MB 的设备树绰绰有余。system 如果放 ext4 rootfs根据你的文件系统大小调整128MB 适合最小系统实际产品按需要扩大。4.3 全流程操作实录我记录一次完整刷写过程包括中间等待时间方便你感受真实的联调节奏。板端执行 fastboot 服务命令 fastboot usb 0主机端$ fastboot devices 1234567890 fastboot看到设备之后先刷 bl2$ fastboot flash bl2 u-boot-bl2.bin Sending bl2 (376 KB) OKAY [ 0.058s] Writing bl2 OKAY [ 0.387s] Finished. Total time: 0.448s然后内核和设备树$ fastboot flash kernel zImage Sending kernel (8192 KB) OKAY [ 0.203s] Writing kernel OKAY [ 0.513s] Finished. Total time: 0.719s $ fastboot flash dtb exynos4412.dtb Sending dtb (64 KB) OKAY [ 0.011s] Writing dtb OKAY [ 0.082s] Finished. Total time: 0.095s刷 system 分区比较久128MB 的 ext4 镜像需要一两分钟$ fastboot flash system rootfs.ext4 Sending system (131072 KB) OKAY [ 3.275s] Writing system OKAY [ 18.647s] Finished. Total time: 21.928s全部完成后不要急着 fastboot reboot先在板端命令行关掉 fastboot 状态CtrlC然后 run bootcmd 验证加载链路。如果每一步的偏移、分区定义都没错串口上就能看到 U-Boot 读 EMMC、解压内核、挂载 rootfs 的完整流程。跑起来之后再把 SD 卡拔掉、拨码切到 EMMC 启动冷启动一次确认断电重启也没问题。这个冷启动验证是最关键的很多板子在 warm reboot 下能过断电后就起不来多半是电源时序或者 BL1 初始化依赖冷启动初始化的问题。5. 常见问题与排查技巧5.1 fastboot 设备不识别fastboot devices 一直不显示设备是我遇到最多的一个问题。排查顺序建议按下面这张表来现象可能原因处理方式fastboot devices 空USB 线接在 HOST 口换到 OTG/Device 口fastboot devices 空板端没执行 fastboot usb 0回 console 确认命令是否卡住提示 waiting for device主机权限不足加 udev 规则或用 sudo 测试设备枚举但刷写失败USB 供电不稳换独立供电的 USB 线或加 Hub这里我想多说一句权限问题。很多人习惯直接用 sudo但后面写脚本自动化刷机时 sudo 反而碍事。更好的做法是插上设备后用lsusb查到 Vendor ID写一条 udev 规则SUBSYSTEMusb, ATTR{idVendor}18d1, MODE0666, GROUPplugdev不同板子的 idVendor 可能不一样4412 平台的 U-Boot fastboot 设备通常模仿 Android 设备的 vendor ID但我遇到过板厂改过的所以一定以lsusb实测为准。不要照抄别人的 ID。5.2 fastboot flash 各种失败flash 阶段最容易出现的几个报错“Invalid partition name”含义是 fastboot 传过来的分区名在 fastboot_partitions 环境变量里找不到。检查环境变量是否保存成功、分区名是否大小写一致。“Partition size too small”含义是镜像大小超过分区 size 定义。这种情况改分区大小别硬塞。“mmc write failed”含义是 EMMC 写保护或者地址越界。4412 的 EMMC 有的出厂会带写保护需要用 mmc 命令解除保护mmc wp 0这条命令在实际项目里并不是所有 EMMC 型号都支持如果报错说明芯片本身没有写保护功能可以跳过。地址越界的问题则要检查随机出现的分区起始地址是否超出了 EMMC 容量。有一个经验fastboot 刷写途中如果断了不要直接重刷先把 EMMC 的头几个扇区读出来确认 BL1 区域有没有被破坏。fastboot 的写入是逐扇区覆盖的如果刷 bl2 分区时传错了镜像很可能把 BL1 所在扇区也覆盖掉导致板子彻底变砖这时只有 SD 卡启动才能救回来。5.3 EMMC 启动阶段的各种卡死从 EMMC 启动的卡死现象不像 fastboot 那么直观但大部分都能从串口输出定位到阶段。卡在 BL1 阶段通常是 BL1 缺失或被覆盖或者 BL2 的头部信息不正确。串口输出一般是“BL1: undefined instruction”或直接没有任何输出。处理办法是重新刷 BL1且确认 BL2 是用 mkimage exynos 或板厂脚本打包过的。卡在 U-Boot 阶段串口有 U-Boot 的 banner 但停在命令行不执行 bootcmd一般是环境变量没保存成功或者 bootcmd 写错了 U-Boot 自己解析不了。先把环境变量用 printenv 打出来逐字检查特别是转义字符和引号。kernel 起来但 rootfs 挂载失败多半是 bootargs 里的 root 设备节点和实际分区对不上。这种情况下不要瞎试回到 fastboot 的分区表把每个分区的起始块换算成 Linux 下的 mmcblk 分区序号再对应修改 root/dev/mmcblk0pX。5.4 排查逻辑的通用方法论最后分享一套我移植多次之后总结的排查逻辑。嵌入式启动问题永远先把现象定位到“iROM / BL1 / U-Boot / Kernel”四层中的某一层。每一层的输出特征都不一样iROM 阶段无任何输出BL1 阶段可能有厂商 logo 或一行字符串U-Boot 阶段有 U-Boot 版本信息和命令行提示符Kernel 阶段有内核日志直到 panic。拿到串口日志后先判断卡在哪一层再对这一层的具体偏移和镜像格式做检查不要一上来就怀疑源码配置。我见过有人在 U-Boot 的 mmc 驱动里折腾半天最后发现只是 BL1 烧的偏移差了几个扇区这就是定位层次错了导致的无效调试。6. 移植完成后的一些体会fastboot 和 EMMC 启动全部跑通之后这块板子才算真正进入了“能用”的状态。我自己的体会是整个移植过程最有价值的不是那些配置项本身而是对启动链路的完整认知——你把 BL1、BL2、kernel 每一级从哪里加载、加载到哪里、凭什么校验搞清楚之后换任何一款新板子都不会心虚。最后再分享一个实用技巧把完整的一套 fastboot 刷写命令写成脚本配合一个标准的镜像目录每次编译完直接执行./flash_all.sh这个脚本里包含分区检查和 flash 命令能避免误刷。脚本不要写死设备 ID每次 fastboot devices 判断一下有设备才继续没有设备就报错退出。这套流程我后来带到别的项目里也一直在用属于移植工程量产阶段最值得投资的小工具。